@reefclaw/connect 0.1.12 → 0.1.13

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 (390) hide show
  1. package/assets/plugin/audit/mode-transition-audit.d.ts +11 -0
  2. package/assets/plugin/audit/mode-transition-audit.js +29 -0
  3. package/assets/plugin/balance-utils.d.ts +36 -0
  4. package/assets/plugin/balance-utils.js +98 -0
  5. package/assets/plugin/ccxt/binance-ban-gate.d.ts +47 -0
  6. package/assets/plugin/ccxt/binance-ban-gate.js +420 -0
  7. package/assets/plugin/ccxt/binance-private.d.ts +346 -0
  8. package/assets/plugin/ccxt/binance-private.js +1525 -0
  9. package/assets/plugin/ccxt/binance-public.d.ts +34 -0
  10. package/assets/plugin/ccxt/binance-public.js +180 -0
  11. package/assets/plugin/ccxt/intel-public.d.ts +25 -0
  12. package/assets/plugin/ccxt/intel-public.js +80 -0
  13. package/assets/plugin/ccxt/public-market-data-api.d.ts +26 -0
  14. package/assets/plugin/ccxt/public-market-data-api.js +23 -0
  15. package/assets/plugin/config/agent-config-client.d.ts +77 -0
  16. package/assets/plugin/config/agent-config-client.js +188 -0
  17. package/assets/plugin/config/agent-config-poller.d.ts +32 -0
  18. package/assets/plugin/config/agent-config-poller.js +101 -0
  19. package/assets/plugin/config/brackets-config.d.ts +22 -0
  20. package/assets/plugin/config/brackets-config.js +58 -0
  21. package/assets/plugin/config/entitlement-gate.d.ts +51 -0
  22. package/assets/plugin/config/entitlement-gate.js +137 -0
  23. package/assets/plugin/config/gate-store.d.ts +18 -0
  24. package/assets/plugin/config/gate-store.js +61 -0
  25. package/assets/plugin/config/plugin-config-io.d.ts +222 -0
  26. package/assets/plugin/config/plugin-config-io.js +126 -0
  27. package/assets/plugin/config/position-review-config.d.ts +35 -0
  28. package/assets/plugin/config/position-review-config.js +105 -0
  29. package/assets/plugin/config/tool-gate.d.ts +53 -0
  30. package/assets/plugin/config/tool-gate.js +128 -0
  31. package/assets/plugin/config/user-data-stream-config.d.ts +89 -0
  32. package/assets/plugin/config/user-data-stream-config.js +239 -0
  33. package/assets/plugin/connector-supervisor.d.ts +36 -0
  34. package/assets/plugin/connector-supervisor.js +149 -0
  35. package/assets/plugin/exchange-adapter.d.ts +65 -0
  36. package/assets/plugin/exchange-adapter.js +4 -0
  37. package/assets/plugin/index.d.ts +30 -0
  38. package/assets/plugin/index.js +2763 -0
  39. package/assets/plugin/ingest/pending-entry-metadata.d.ts +74 -0
  40. package/assets/plugin/ingest/pending-entry-metadata.js +236 -0
  41. package/assets/plugin/ingest/position-auto-capture.d.ts +171 -0
  42. package/assets/plugin/ingest/position-auto-capture.js +709 -0
  43. package/assets/plugin/ingest/position-decisions-client.d.ts +327 -0
  44. package/assets/plugin/ingest/position-decisions-client.js +306 -0
  45. package/assets/plugin/ingest/readiness-reporter.d.ts +42 -0
  46. package/assets/plugin/ingest/readiness-reporter.js +163 -0
  47. package/assets/plugin/ingest/reconcile-db-vs-exchange.d.ts +51 -0
  48. package/assets/plugin/ingest/reconcile-db-vs-exchange.js +170 -0
  49. package/assets/plugin/ingest/reconciler-cleanup.d.ts +37 -0
  50. package/assets/plugin/ingest/reconciler-cleanup.js +147 -0
  51. package/assets/plugin/ingest/rest-gap-filler.d.ts +191 -0
  52. package/assets/plugin/ingest/rest-gap-filler.js +565 -0
  53. package/assets/plugin/ingest/touched-symbols-store.d.ts +25 -0
  54. package/assets/plugin/ingest/touched-symbols-store.js +96 -0
  55. package/assets/plugin/ingest/trade-store-client.d.ts +40 -0
  56. package/assets/plugin/ingest/trade-store-client.js +125 -0
  57. package/assets/plugin/ingest/ws-ingest.d.ts +43 -0
  58. package/assets/plugin/ingest/ws-ingest.js +126 -0
  59. package/assets/plugin/learning/setup-family.d.ts +21 -0
  60. package/assets/plugin/learning/setup-family.js +103 -0
  61. package/assets/plugin/lifecycle/install-signal-handlers.d.ts +33 -0
  62. package/assets/plugin/lifecycle/install-signal-handlers.js +112 -0
  63. package/assets/plugin/lifecycle/shutdown-coordinator.d.ts +43 -0
  64. package/assets/plugin/lifecycle/shutdown-coordinator.js +131 -0
  65. package/assets/plugin/lifecycle/trading-operation-lock.d.ts +17 -0
  66. package/assets/plugin/lifecycle/trading-operation-lock.js +14 -0
  67. package/assets/plugin/live/bracket-id.d.ts +26 -0
  68. package/assets/plugin/live/bracket-id.js +112 -0
  69. package/assets/plugin/live/bracket-ledger.d.ts +54 -0
  70. package/assets/plugin/live/bracket-ledger.js +267 -0
  71. package/assets/plugin/live/bracket-manager.d.ts +82 -0
  72. package/assets/plugin/live/bracket-manager.js +478 -0
  73. package/assets/plugin/live/bracket-params.d.ts +22 -0
  74. package/assets/plugin/live/bracket-params.js +124 -0
  75. package/assets/plugin/live/bracket-reconciler.d.ts +95 -0
  76. package/assets/plugin/live/bracket-reconciler.js +573 -0
  77. package/assets/plugin/live/bracket-types.d.ts +102 -0
  78. package/assets/plugin/live/bracket-types.js +8 -0
  79. package/assets/plugin/live/deposit-tracker.d.ts +62 -0
  80. package/assets/plugin/live/deposit-tracker.js +97 -0
  81. package/assets/plugin/live/emergency-controls.d.ts +32 -0
  82. package/assets/plugin/live/emergency-controls.js +226 -0
  83. package/assets/plugin/live/exchange-errors.d.ts +12 -0
  84. package/assets/plugin/live/exchange-errors.js +130 -0
  85. package/assets/plugin/live/exchange-info-cache.d.ts +37 -0
  86. package/assets/plugin/live/exchange-info-cache.js +134 -0
  87. package/assets/plugin/live/fact-subscriber.d.ts +78 -0
  88. package/assets/plugin/live/fact-subscriber.js +182 -0
  89. package/assets/plugin/live/fill-price.d.ts +13 -0
  90. package/assets/plugin/live/fill-price.js +37 -0
  91. package/assets/plugin/live/intent-journal.d.ts +42 -0
  92. package/assets/plugin/live/intent-journal.js +122 -0
  93. package/assets/plugin/live/listen-key-manager.d.ts +70 -0
  94. package/assets/plugin/live/listen-key-manager.js +169 -0
  95. package/assets/plugin/live/live-adapter.d.ts +319 -0
  96. package/assets/plugin/live/live-adapter.js +1906 -0
  97. package/assets/plugin/live/live-balance-enricher.d.ts +32 -0
  98. package/assets/plugin/live/live-balance-enricher.js +104 -0
  99. package/assets/plugin/live/live-bracket-api.d.ts +13 -0
  100. package/assets/plugin/live/live-bracket-api.js +20 -0
  101. package/assets/plugin/live/live-state-store.d.ts +194 -0
  102. package/assets/plugin/live/live-state-store.js +450 -0
  103. package/assets/plugin/live/local-signal-service.d.ts +57 -0
  104. package/assets/plugin/live/local-signal-service.js +151 -0
  105. package/assets/plugin/live/local-strategy-evaluator.d.ts +62 -0
  106. package/assets/plugin/live/local-strategy-evaluator.js +131 -0
  107. package/assets/plugin/live/microstructure-assembler.d.ts +54 -0
  108. package/assets/plugin/live/microstructure-assembler.js +148 -0
  109. package/assets/plugin/live/order-poller.d.ts +29 -0
  110. package/assets/plugin/live/order-poller.js +125 -0
  111. package/assets/plugin/live/position-state-store.d.ts +87 -0
  112. package/assets/plugin/live/position-state-store.js +237 -0
  113. package/assets/plugin/live/proposal-decision-listener.d.ts +70 -0
  114. package/assets/plugin/live/proposal-decision-listener.js +292 -0
  115. package/assets/plugin/live/proposal-manager.d.ts +76 -0
  116. package/assets/plugin/live/proposal-manager.js +140 -0
  117. package/assets/plugin/live/rate-limiter.d.ts +47 -0
  118. package/assets/plugin/live/rate-limiter.js +159 -0
  119. package/assets/plugin/live/reconciler.d.ts +39 -0
  120. package/assets/plugin/live/reconciler.js +175 -0
  121. package/assets/plugin/live/setup-buckets.d.ts +7 -0
  122. package/assets/plugin/live/setup-buckets.js +33 -0
  123. package/assets/plugin/live/slippage-tracker.d.ts +45 -0
  124. package/assets/plugin/live/slippage-tracker.js +78 -0
  125. package/assets/plugin/live/stop-watcher.d.ts +67 -0
  126. package/assets/plugin/live/stop-watcher.js +218 -0
  127. package/assets/plugin/live/user-data-active-probe.d.ts +54 -0
  128. package/assets/plugin/live/user-data-active-probe.js +180 -0
  129. package/assets/plugin/live/user-data-stream-controller.d.ts +200 -0
  130. package/assets/plugin/live/user-data-stream-controller.js +579 -0
  131. package/assets/plugin/live/user-data-stream-ws.d.ts +22 -0
  132. package/assets/plugin/live/user-data-stream-ws.js +63 -0
  133. package/assets/plugin/live/user-data-stream.d.ts +243 -0
  134. package/assets/plugin/live/user-data-stream.js +704 -0
  135. package/assets/plugin/logger.d.ts +2 -0
  136. package/assets/plugin/logger.js +2 -0
  137. package/assets/plugin/mfe.d.ts +21 -0
  138. package/assets/plugin/mfe.js +68 -0
  139. package/assets/plugin/onboarding/mode-ladder.d.ts +1 -0
  140. package/assets/plugin/onboarding/mode-ladder.js +3 -0
  141. package/assets/plugin/onboarding/runtime.d.ts +102 -0
  142. package/assets/plugin/onboarding/runtime.js +215 -0
  143. package/assets/plugin/openclaw.plugin.json +92 -0
  144. package/assets/plugin/paper-adapter.d.ts +24 -0
  145. package/assets/plugin/paper-adapter.js +91 -0
  146. package/assets/plugin/persistence/state-manager.d.ts +66 -0
  147. package/assets/plugin/persistence/state-manager.js +222 -0
  148. package/assets/plugin/pinned-plan.d.ts +9 -0
  149. package/assets/plugin/pinned-plan.js +23 -0
  150. package/assets/plugin/portfolio/reentry-tracker.d.ts +36 -0
  151. package/assets/plugin/portfolio/reentry-tracker.js +127 -0
  152. package/assets/plugin/portfolio/wave9-admission.d.ts +67 -0
  153. package/assets/plugin/portfolio/wave9-admission.js +262 -0
  154. package/assets/plugin/portfolio/wave9-policy.d.ts +36 -0
  155. package/assets/plugin/portfolio/wave9-policy.js +183 -0
  156. package/assets/plugin/risk/pre-trade-check.d.ts +38 -0
  157. package/assets/plugin/risk/pre-trade-check.js +345 -0
  158. package/assets/plugin/risk/pre-trade-types.d.ts +60 -0
  159. package/assets/plugin/risk/pre-trade-types.js +3 -0
  160. package/assets/plugin/shadow/shadow-tracker.d.ts +36 -0
  161. package/assets/plugin/shadow/shadow-tracker.js +151 -0
  162. package/assets/plugin/shadow/types.d.ts +42 -0
  163. package/assets/plugin/shadow/types.js +20 -0
  164. package/assets/plugin/shared/indicators-extended.d.ts +52 -0
  165. package/assets/plugin/shared/indicators-extended.js +291 -0
  166. package/assets/plugin/shared/indicators.d.ts +15 -0
  167. package/assets/plugin/shared/indicators.js +114 -0
  168. package/assets/plugin/signals/conditions/registry.d.ts +16 -0
  169. package/assets/plugin/signals/conditions/registry.js +1333 -0
  170. package/assets/plugin/signals/conditions/types.d.ts +1 -0
  171. package/assets/plugin/signals/conditions/types.js +4 -0
  172. package/assets/plugin/signals/direction-rules.d.ts +3 -0
  173. package/assets/plugin/signals/direction-rules.js +24 -0
  174. package/assets/plugin/signals/entry-rules.d.ts +6 -0
  175. package/assets/plugin/signals/entry-rules.js +33 -0
  176. package/assets/plugin/signals/serialize-context.d.ts +4 -0
  177. package/assets/plugin/signals/serialize-context.js +39 -0
  178. package/assets/plugin/signals/stop-rules.d.ts +3 -0
  179. package/assets/plugin/signals/stop-rules.js +48 -0
  180. package/assets/plugin/signals/strategy-adapter.d.ts +47 -0
  181. package/assets/plugin/signals/strategy-adapter.js +209 -0
  182. package/assets/plugin/signals/types.d.ts +1 -0
  183. package/assets/plugin/signals/types.js +8 -0
  184. package/assets/plugin/simulator/exchange-simulator.d.ts +105 -0
  185. package/assets/plugin/simulator/exchange-simulator.js +759 -0
  186. package/assets/plugin/simulator/fill-engine.d.ts +53 -0
  187. package/assets/plugin/simulator/fill-engine.js +280 -0
  188. package/assets/plugin/simulator/paper-market-feed.d.ts +26 -0
  189. package/assets/plugin/simulator/paper-market-feed.js +104 -0
  190. package/assets/plugin/simulator/realistic-fills.d.ts +59 -0
  191. package/assets/plugin/simulator/realistic-fills.js +175 -0
  192. package/assets/plugin/simulator/types.d.ts +228 -0
  193. package/assets/plugin/simulator/types.js +43 -0
  194. package/assets/plugin/strategy/builtin-strategies.d.ts +2 -0
  195. package/assets/plugin/strategy/builtin-strategies.js +113 -0
  196. package/assets/plugin/strategy/condition-registry.d.ts +3 -0
  197. package/assets/plugin/strategy/condition-registry.js +153 -0
  198. package/assets/plugin/strategy/evaluator.d.ts +70 -0
  199. package/assets/plugin/strategy/evaluator.js +98 -0
  200. package/assets/plugin/tools/assessment-validation.d.ts +141 -0
  201. package/assets/plugin/tools/assessment-validation.js +473 -0
  202. package/assets/plugin/tools/attach-brackets.d.ts +39 -0
  203. package/assets/plugin/tools/attach-brackets.js +564 -0
  204. package/assets/plugin/tools/audit-bracket-protection.d.ts +49 -0
  205. package/assets/plugin/tools/audit-bracket-protection.js +683 -0
  206. package/assets/plugin/tools/bracket-control.d.ts +12 -0
  207. package/assets/plugin/tools/bracket-control.js +35 -0
  208. package/assets/plugin/tools/cancel-all-orders.d.ts +9 -0
  209. package/assets/plugin/tools/cancel-all-orders.js +8 -0
  210. package/assets/plugin/tools/cancel-order.d.ts +14 -0
  211. package/assets/plugin/tools/cancel-order.js +60 -0
  212. package/assets/plugin/tools/check-position-health.d.ts +46 -0
  213. package/assets/plugin/tools/check-position-health.js +194 -0
  214. package/assets/plugin/tools/clear-exchange-credentials.d.ts +24 -0
  215. package/assets/plugin/tools/clear-exchange-credentials.js +70 -0
  216. package/assets/plugin/tools/close-position.d.ts +45 -0
  217. package/assets/plugin/tools/close-position.js +722 -0
  218. package/assets/plugin/tools/create-order.d.ts +89 -0
  219. package/assets/plugin/tools/create-order.js +1555 -0
  220. package/assets/plugin/tools/exit-gate.d.ts +58 -0
  221. package/assets/plugin/tools/exit-gate.js +162 -0
  222. package/assets/plugin/tools/fetch-balance.d.ts +5 -0
  223. package/assets/plugin/tools/fetch-balance.js +4 -0
  224. package/assets/plugin/tools/fetch-ohlcv.d.ts +11 -0
  225. package/assets/plugin/tools/fetch-ohlcv.js +8 -0
  226. package/assets/plugin/tools/fetch-open-orders.d.ts +7 -0
  227. package/assets/plugin/tools/fetch-open-orders.js +4 -0
  228. package/assets/plugin/tools/fetch-positions.d.ts +7 -0
  229. package/assets/plugin/tools/fetch-positions.js +4 -0
  230. package/assets/plugin/tools/fetch-ticker.d.ts +11 -0
  231. package/assets/plugin/tools/fetch-ticker.js +5 -0
  232. package/assets/plugin/tools/get-agent-profile.d.ts +4 -0
  233. package/assets/plugin/tools/get-agent-profile.js +6 -0
  234. package/assets/plugin/tools/get-analytics.d.ts +6 -0
  235. package/assets/plugin/tools/get-analytics.js +7 -0
  236. package/assets/plugin/tools/get-backtest.d.ts +12 -0
  237. package/assets/plugin/tools/get-backtest.js +91 -0
  238. package/assets/plugin/tools/get-basis.d.ts +7 -0
  239. package/assets/plugin/tools/get-basis.js +7 -0
  240. package/assets/plugin/tools/get-bracket-config.d.ts +11 -0
  241. package/assets/plugin/tools/get-bracket-config.js +24 -0
  242. package/assets/plugin/tools/get-cascade-risk.d.ts +7 -0
  243. package/assets/plugin/tools/get-cascade-risk.js +8 -0
  244. package/assets/plugin/tools/get-crypto-metrics.d.ts +18 -0
  245. package/assets/plugin/tools/get-crypto-metrics.js +62 -0
  246. package/assets/plugin/tools/get-cvd.d.ts +6 -0
  247. package/assets/plugin/tools/get-cvd.js +6 -0
  248. package/assets/plugin/tools/get-divergences.d.ts +6 -0
  249. package/assets/plugin/tools/get-divergences.js +6 -0
  250. package/assets/plugin/tools/get-funding-context.d.ts +6 -0
  251. package/assets/plugin/tools/get-funding-context.js +16 -0
  252. package/assets/plugin/tools/get-liquidation-levels.d.ts +7 -0
  253. package/assets/plugin/tools/get-liquidation-levels.js +7 -0
  254. package/assets/plugin/tools/get-liquidation-pulse.d.ts +9 -0
  255. package/assets/plugin/tools/get-liquidation-pulse.js +22 -0
  256. package/assets/plugin/tools/get-market-breadth.d.ts +6 -0
  257. package/assets/plugin/tools/get-market-breadth.js +8 -0
  258. package/assets/plugin/tools/get-market-intel.d.ts +19 -0
  259. package/assets/plugin/tools/get-market-intel.js +116 -0
  260. package/assets/plugin/tools/get-market-structure.d.ts +47 -0
  261. package/assets/plugin/tools/get-market-structure.js +198 -0
  262. package/assets/plugin/tools/get-my-mined-patterns.d.ts +20 -0
  263. package/assets/plugin/tools/get-my-mined-patterns.js +61 -0
  264. package/assets/plugin/tools/get-my-proposed-learnings.d.ts +20 -0
  265. package/assets/plugin/tools/get-my-proposed-learnings.js +55 -0
  266. package/assets/plugin/tools/get-my-recent-reviews.d.ts +22 -0
  267. package/assets/plugin/tools/get-my-recent-reviews.js +66 -0
  268. package/assets/plugin/tools/get-orderbook.d.ts +21 -0
  269. package/assets/plugin/tools/get-orderbook.js +32 -0
  270. package/assets/plugin/tools/get-pattern-scan.d.ts +7 -0
  271. package/assets/plugin/tools/get-pattern-scan.js +8 -0
  272. package/assets/plugin/tools/get-regime.d.ts +6 -0
  273. package/assets/plugin/tools/get-regime.js +7 -0
  274. package/assets/plugin/tools/get-relevant-learnings.d.ts +21 -0
  275. package/assets/plugin/tools/get-relevant-learnings.js +65 -0
  276. package/assets/plugin/tools/get-resting-liquidity.d.ts +6 -0
  277. package/assets/plugin/tools/get-resting-liquidity.js +11 -0
  278. package/assets/plugin/tools/get-risk-scenario.d.ts +29 -0
  279. package/assets/plugin/tools/get-risk-scenario.js +47 -0
  280. package/assets/plugin/tools/get-risk-summary.d.ts +51 -0
  281. package/assets/plugin/tools/get-risk-summary.js +118 -0
  282. package/assets/plugin/tools/get-sentiment.d.ts +4 -0
  283. package/assets/plugin/tools/get-sentiment.js +6 -0
  284. package/assets/plugin/tools/get-session-review.d.ts +7 -0
  285. package/assets/plugin/tools/get-session-review.js +8 -0
  286. package/assets/plugin/tools/get-setup-detail.d.ts +7 -0
  287. package/assets/plugin/tools/get-setup-detail.js +322 -0
  288. package/assets/plugin/tools/get-signals.d.ts +15 -0
  289. package/assets/plugin/tools/get-signals.js +54 -0
  290. package/assets/plugin/tools/get-sizing.d.ts +6 -0
  291. package/assets/plugin/tools/get-sizing.js +6 -0
  292. package/assets/plugin/tools/get-trade-feedback.d.ts +7 -0
  293. package/assets/plugin/tools/get-trade-feedback.js +8 -0
  294. package/assets/plugin/tools/get-trade-flow.d.ts +7 -0
  295. package/assets/plugin/tools/get-trade-flow.js +7 -0
  296. package/assets/plugin/tools/get-volume-analysis.d.ts +21 -0
  297. package/assets/plugin/tools/get-volume-analysis.js +74 -0
  298. package/assets/plugin/tools/get-volume-profile.d.ts +7 -0
  299. package/assets/plugin/tools/get-volume-profile.js +7 -0
  300. package/assets/plugin/tools/get-wave9-status.d.ts +127 -0
  301. package/assets/plugin/tools/get-wave9-status.js +796 -0
  302. package/assets/plugin/tools/helpers.d.ts +26 -0
  303. package/assets/plugin/tools/helpers.js +39 -0
  304. package/assets/plugin/tools/intel-api.d.ts +34 -0
  305. package/assets/plugin/tools/intel-api.js +119 -0
  306. package/assets/plugin/tools/intel-cache.d.ts +25 -0
  307. package/assets/plugin/tools/intel-cache.js +148 -0
  308. package/assets/plugin/tools/list-strategies.d.ts +17 -0
  309. package/assets/plugin/tools/list-strategies.js +23 -0
  310. package/assets/plugin/tools/modify-stop.d.ts +21 -0
  311. package/assets/plugin/tools/modify-stop.js +120 -0
  312. package/assets/plugin/tools/modify-target.d.ts +21 -0
  313. package/assets/plugin/tools/modify-target.js +110 -0
  314. package/assets/plugin/tools/propose-learning.d.ts +22 -0
  315. package/assets/plugin/tools/propose-learning.js +65 -0
  316. package/assets/plugin/tools/query-review-outcomes.d.ts +30 -0
  317. package/assets/plugin/tools/query-review-outcomes.js +64 -0
  318. package/assets/plugin/tools/query-trades.d.ts +21 -0
  319. package/assets/plugin/tools/query-trades.js +37 -0
  320. package/assets/plugin/tools/record-position-reviews.d.ts +38 -0
  321. package/assets/plugin/tools/record-position-reviews.js +147 -0
  322. package/assets/plugin/tools/save-strategy.d.ts +16 -0
  323. package/assets/plugin/tools/save-strategy.js +46 -0
  324. package/assets/plugin/tools/scan-pairs.d.ts +22 -0
  325. package/assets/plugin/tools/scan-pairs.js +234 -0
  326. package/assets/plugin/tools/score-setup.d.ts +31 -0
  327. package/assets/plugin/tools/score-setup.js +268 -0
  328. package/assets/plugin/tools/set-bracket-requirement.d.ts +18 -0
  329. package/assets/plugin/tools/set-bracket-requirement.js +81 -0
  330. package/assets/plugin/tools/set-exchange-credentials.d.ts +25 -0
  331. package/assets/plugin/tools/set-exchange-credentials.js +80 -0
  332. package/assets/plugin/tools/set-trading-mode.d.ts +26 -0
  333. package/assets/plugin/tools/set-trading-mode.js +152 -0
  334. package/assets/plugin/tools/test-exchange-credentials.d.ts +16 -0
  335. package/assets/plugin/tools/test-exchange-credentials.js +100 -0
  336. package/assets/plugin/tools/toggle-strategy.d.ts +8 -0
  337. package/assets/plugin/tools/toggle-strategy.js +15 -0
  338. package/assets/plugin/trading-params-cache.d.ts +26 -0
  339. package/assets/plugin/trading-params-cache.js +52 -0
  340. package/assets/plugin/types.d.ts +134 -0
  341. package/assets/plugin/types.js +7 -0
  342. package/assets/plugin/util/plugin-paths.d.ts +3 -0
  343. package/assets/plugin/util/plugin-paths.js +15 -0
  344. package/assets/plugin/venues/hyperliquid/hl-balance.d.ts +116 -0
  345. package/assets/plugin/venues/hyperliquid/hl-balance.js +145 -0
  346. package/assets/plugin/venues/hyperliquid/hl-bracket-coordinator.d.ts +123 -0
  347. package/assets/plugin/venues/hyperliquid/hl-bracket-coordinator.js +533 -0
  348. package/assets/plugin/venues/hyperliquid/hl-brackets.d.ts +102 -0
  349. package/assets/plugin/venues/hyperliquid/hl-brackets.js +172 -0
  350. package/assets/plugin/venues/hyperliquid/hl-cloid.d.ts +22 -0
  351. package/assets/plugin/venues/hyperliquid/hl-cloid.js +82 -0
  352. package/assets/plugin/venues/hyperliquid/hl-info-cache.d.ts +46 -0
  353. package/assets/plugin/venues/hyperliquid/hl-info-cache.js +125 -0
  354. package/assets/plugin/venues/hyperliquid/hl-live-adapter.d.ts +146 -0
  355. package/assets/plugin/venues/hyperliquid/hl-live-adapter.js +728 -0
  356. package/assets/plugin/venues/hyperliquid/hl-precision.d.ts +61 -0
  357. package/assets/plugin/venues/hyperliquid/hl-precision.js +176 -0
  358. package/assets/plugin/venues/hyperliquid/hl-private.d.ts +88 -0
  359. package/assets/plugin/venues/hyperliquid/hl-private.js +357 -0
  360. package/assets/plugin/venues/hyperliquid/hl-public.d.ts +79 -0
  361. package/assets/plugin/venues/hyperliquid/hl-public.js +436 -0
  362. package/assets/plugin/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
  363. package/assets/plugin/venues/hyperliquid/hl-rate-gate.js +220 -0
  364. package/assets/plugin/venues/hyperliquid/hl-user-stream.d.ts +90 -0
  365. package/assets/plugin/venues/hyperliquid/hl-user-stream.js +220 -0
  366. package/assets/plugin/venues/registry.d.ts +38 -0
  367. package/assets/plugin/venues/registry.js +46 -0
  368. package/assets/plugin/venues/symbols.d.ts +43 -0
  369. package/assets/plugin/venues/symbols.js +107 -0
  370. package/assets/plugin/wave9/live-account-capture.d.ts +67 -0
  371. package/assets/plugin/wave9/live-account-capture.js +435 -0
  372. package/assets/plugin/wave9/live-autonomous-protection.d.ts +39 -0
  373. package/assets/plugin/wave9/live-autonomous-protection.js +112 -0
  374. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
  375. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.js +115 -0
  376. package/assets/plugin/wave9/live-execution-ledger.d.ts +107 -0
  377. package/assets/plugin/wave9/live-execution-ledger.js +498 -0
  378. package/assets/plugin/wave9/live-position-confirmation.d.ts +18 -0
  379. package/assets/plugin/wave9/live-position-confirmation.js +111 -0
  380. package/assets/plugin/wave9/live-residual-protection.d.ts +18 -0
  381. package/assets/plugin/wave9/live-residual-protection.js +250 -0
  382. package/assets/plugin/wave9/live-startup-reconciliation.d.ts +38 -0
  383. package/assets/plugin/wave9/live-startup-reconciliation.js +454 -0
  384. package/assets/plugin/wave9/live-symbol-ownership.d.ts +20 -0
  385. package/assets/plugin/wave9/live-symbol-ownership.js +132 -0
  386. package/assets/plugin/wave9/paper-admission-guard.d.ts +199 -0
  387. package/assets/plugin/wave9/paper-admission-guard.js +650 -0
  388. package/assets/plugin/wave9/usdm-evidence-provider.d.ts +42 -0
  389. package/assets/plugin/wave9/usdm-evidence-provider.js +133 -0
  390. package/package.json +1 -1
@@ -0,0 +1,1555 @@
1
+ // Tool: create_order — order execution with real price data + pre-trade risk gate
2
+ // Readiness gate: BLOCKED unless adapter.readiness === 'READY'.
3
+ import { randomUUID } from 'node:crypto';
4
+ import { formatError } from '../logger.js';
5
+ import { getQuoteBalance, getQuoteWalletBalance } from '../balance-utils.js';
6
+ import { fetchCurrentPrice, fetchOrderBook, isError } from './helpers.js';
7
+ import { validateCreateOrder, sanitizeRealizationRule, validateProtectiveGeometry } from './assessment-validation.js';
8
+ import { preTradeRiskCheck, getDefaultPreTradeLimits, computeConsecutiveLosses } from '../risk/pre-trade-check.js';
9
+ import { bracketsEnabled, loadBracketMode, loadBracketRequirements } from '../config/brackets-config.js';
10
+ import { onCreateOrderFilled } from '../ingest/position-auto-capture.js';
11
+ import { wave9ClientOrderId, } from '../wave9/live-execution-ledger.js';
12
+ import { inspectWave9LiveSymbolOwnership, } from '../wave9/live-symbol-ownership.js';
13
+ import { confirmWave9LivePositionFlat } from '../wave9/live-position-confirmation.js';
14
+ import { HyperliquidLiveAdapter } from '../venues/hyperliquid/hl-live-adapter.js';
15
+ import { buildHlOrderCloid } from '../venues/hyperliquid/hl-cloid.js';
16
+ import { reprotectWave9LiveResidual, } from '../wave9/live-residual-protection.js';
17
+ import { captureWave9PaperAccountSnapshot, isWave9ManagedPosition, WAVE9_BUNDLE_SETUP_TYPE, } from '../wave9/paper-admission-guard.js';
18
+ function currentWave9ExecutionMode(deps) {
19
+ const runtimeMode = deps.wave9TradingModeCheck?.() ?? deps.adapter.mode;
20
+ if (!deps.adapter.isLive && deps.adapter.mode === 'PAPER' && runtimeMode === 'PAPER')
21
+ return 'PAPER';
22
+ if (deps.adapter.isLive && deps.adapter.mode === 'LIVE' && runtimeMode === 'LIVE')
23
+ return 'LIVE';
24
+ return null;
25
+ }
26
+ function residualProtectionMessage(outcome) {
27
+ const protection = outcome.residualProtection;
28
+ if (!protection)
29
+ return ' Residual protection state is unavailable.';
30
+ return protection.status === 'protected'
31
+ ? ` The residual is protected by the proven frozen native stop ${protection.stopPrice}` +
32
+ `${protection.stopClientOrderId ? ` (${protection.stopClientOrderId})` : ''}.`
33
+ : ` ${protection.detail}`;
34
+ }
35
+ function confirmedWave9EntryPrice(order, side, stopPrice) {
36
+ const average = typeof order.average === 'number'
37
+ && Number.isFinite(order.average)
38
+ && order.average > 0
39
+ ? order.average
40
+ : undefined;
41
+ const limitPrice = typeof order.price === 'number'
42
+ && Number.isFinite(order.price)
43
+ && order.price > 0
44
+ ? order.price
45
+ : undefined;
46
+ const responsePrice = average ?? limitPrice;
47
+ if (responsePrice === undefined)
48
+ return undefined;
49
+ return side === 'buy'
50
+ ? (responsePrice > stopPrice ? responsePrice : undefined)
51
+ : (responsePrice < stopPrice ? responsePrice : undefined);
52
+ }
53
+ function confirmedWave9ExchangeOrderId(order) {
54
+ return typeof order.id === 'string'
55
+ && order.id.length > 0
56
+ && order.id.length <= 512
57
+ && order.id.trim() === order.id
58
+ ? order.id
59
+ : undefined;
60
+ }
61
+ async function flattenFailedWave9LiveEntry(adapter, ledger, symbol, candidateId, identity, residualProtector = reprotectWave9LiveResidual) {
62
+ let observation = await observeWave9LiveResidual(adapter, symbol, identity.side);
63
+ if (observation.status !== 'visible') {
64
+ const alreadyFlat = await confirmWave9LivePositionFlat(adapter, symbol);
65
+ if (alreadyFlat.confirmed) {
66
+ let terminalPersistenceError;
67
+ try {
68
+ await ledger.markClosed(candidateId);
69
+ }
70
+ catch (err) {
71
+ terminalPersistenceError = formatError(err);
72
+ }
73
+ return {
74
+ confirmedFlat: true,
75
+ confirmationDetail: alreadyFlat.detail,
76
+ closeAttempted: false,
77
+ ...(terminalPersistenceError ? { terminalPersistenceError } : {}),
78
+ };
79
+ }
80
+ // A terminal market fill can become visible during the stable-flat
81
+ // window. Re-read once as positive identity evidence; the failed flat
82
+ // proof itself never authorizes a symbol-wide mutation.
83
+ observation = await observeWave9LiveResidual(adapter, symbol, identity.side);
84
+ }
85
+ const residualQuantity = observation.status === 'visible'
86
+ ? observation.quantity
87
+ : undefined;
88
+ if (!Number.isFinite(identity.maximumQuantity)
89
+ || identity.maximumQuantity <= 0
90
+ || residualQuantity === undefined
91
+ || !Number.isFinite(residualQuantity)
92
+ || residualQuantity <= 0
93
+ || residualQuantity > identity.maximumQuantity) {
94
+ let stopPrice = 0;
95
+ try {
96
+ stopPrice = ledger.getByCandidateId(candidateId)?.initialStop ?? 0;
97
+ }
98
+ catch {
99
+ // Keep the identity refusal as the primary error.
100
+ }
101
+ return {
102
+ confirmedFlat: false,
103
+ confirmationDetail: `Wave 9 exposure identity fence refused symbol-wide flatten: ${observation.detail}; ` +
104
+ `maximum attributable quantity ${identity.maximumQuantity}`,
105
+ closeAttempted: false,
106
+ residualProtection: {
107
+ status: 'manual_intervention',
108
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: refusing native-stop replacement or ` +
109
+ `symbol-wide flatten because current exposure is not exactly candidate-bound ` +
110
+ `(${observation.detail}; maximum ${identity.maximumQuantity})`,
111
+ candidateId,
112
+ symbol,
113
+ stopPrice,
114
+ ...(residualQuantity !== undefined ? { residualQuantity } : {}),
115
+ },
116
+ };
117
+ }
118
+ let flattenError;
119
+ try {
120
+ await adapter.closePosition(symbol, 'bracket_attach_failed');
121
+ }
122
+ catch (err) {
123
+ flattenError = formatError(err);
124
+ }
125
+ const confirmation = await confirmWave9LivePositionFlat(adapter, symbol);
126
+ const terminalSafeFlat = confirmation.confirmed && flattenError === undefined;
127
+ let terminalPersistenceError;
128
+ if (terminalSafeFlat) {
129
+ try {
130
+ await ledger.markClosed(candidateId);
131
+ }
132
+ catch (err) {
133
+ terminalPersistenceError = formatError(err);
134
+ }
135
+ }
136
+ let residualProtection;
137
+ if (!confirmation.confirmed) {
138
+ try {
139
+ residualProtection = await residualProtector(adapter, ledger, candidateId, symbol);
140
+ }
141
+ catch (err) {
142
+ let stopPrice = 0;
143
+ try {
144
+ stopPrice = ledger.getByCandidateId(candidateId)?.initialStop ?? 0;
145
+ }
146
+ catch {
147
+ // Preserve the original re-protection failure in the escalation.
148
+ }
149
+ residualProtection = {
150
+ status: 'manual_intervention',
151
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: residual re-protection threw: ${formatError(err)}`,
152
+ candidateId,
153
+ symbol,
154
+ stopPrice,
155
+ };
156
+ }
157
+ }
158
+ return {
159
+ confirmedFlat: terminalSafeFlat,
160
+ confirmationDetail: confirmation.confirmed && flattenError
161
+ ? `trusted flat snapshots were observed, but the emergency close mutation was not acknowledged ` +
162
+ `(${flattenError}); positive-fill settlement may still be lagging`
163
+ : confirmation.detail,
164
+ closeAttempted: true,
165
+ ...(flattenError ? { flattenError } : {}),
166
+ ...(terminalPersistenceError ? { terminalPersistenceError } : {}),
167
+ ...(residualProtection ? { residualProtection } : {}),
168
+ };
169
+ }
170
+ function canonicalWave9Symbol(symbol) {
171
+ return symbol.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
172
+ }
173
+ async function observeWave9LiveResidual(adapter, symbol, side) {
174
+ let positions;
175
+ try {
176
+ positions = await adapter.getPositionsOrNull(symbol);
177
+ }
178
+ catch (err) {
179
+ return { status: 'unknown', detail: `position read threw: ${formatError(err)}` };
180
+ }
181
+ if (positions === null || !Array.isArray(positions)) {
182
+ return { status: 'unknown', detail: 'position state is unknown' };
183
+ }
184
+ const target = canonicalWave9Symbol(symbol);
185
+ const expectedPositionSide = side === 'buy' ? 'long' : 'short';
186
+ const visible = [];
187
+ for (const position of positions) {
188
+ if (!position
189
+ || typeof position.symbol !== 'string'
190
+ || typeof position.contracts !== 'number'
191
+ || !Number.isFinite(position.contracts)
192
+ || position.contracts < 0) {
193
+ return { status: 'unknown', detail: 'position snapshot contained malformed rows' };
194
+ }
195
+ if (canonicalWave9Symbol(position.symbol) !== target || position.contracts === 0)
196
+ continue;
197
+ if (position.side !== expectedPositionSide) {
198
+ return {
199
+ status: 'unknown',
200
+ detail: `visible residual side ${position.side} does not match ${expectedPositionSide}`,
201
+ };
202
+ }
203
+ visible.push(position);
204
+ }
205
+ if (visible.length === 0)
206
+ return { status: 'flat', detail: 'one trusted snapshot is currently empty' };
207
+ if (visible.length !== 1) {
208
+ return { status: 'unknown', detail: 'multiple matching residual position rows were returned' };
209
+ }
210
+ return {
211
+ status: 'visible',
212
+ detail: `exchange reports ${visible[0].contracts} residual contracts`,
213
+ quantity: visible[0].contracts,
214
+ };
215
+ }
216
+ function candidateBoundFoundOrder(resolution, clientOrderId, symbol, side) {
217
+ if (resolution.status !== 'found')
218
+ return undefined;
219
+ const order = resolution.order;
220
+ if (order.clientOrderId !== clientOrderId
221
+ || canonicalWave9Symbol(order.symbol) !== canonicalWave9Symbol(symbol)
222
+ || order.side !== side
223
+ || order.type !== 'market'
224
+ || confirmedWave9ExchangeOrderId(order) === undefined
225
+ || (order.status !== 'open' && order.status !== 'closed' && order.status !== 'canceled')
226
+ || typeof order.amount !== 'number'
227
+ || !Number.isFinite(order.amount)
228
+ || order.amount <= 0
229
+ || typeof order.filled !== 'number'
230
+ || !Number.isFinite(order.filled)
231
+ || order.filled < 0
232
+ || order.filled > order.amount + 1e-9 * Math.max(1, order.amount))
233
+ return undefined;
234
+ return order;
235
+ }
236
+ /** Venue-aware idempotency cid for the live entry stash. HL client-order-ids
237
+ * are 128-bit hex (0x + 32 hex chars) — a dashed randomUUID() fails
238
+ * hl-private's cloid validation and the ENTRY IS REJECTED before reaching
239
+ * the exchange (found live by the go-live rehearsal's first dust trade).
240
+ * buildHlOrderCloid() mints the 0x0d… ORDER prefix, which the bracket parser
241
+ * deliberately never recognises (cross-scheme cancels are destructive). */
242
+ export function mintEntryStashCid(adapter) {
243
+ return adapter instanceof HyperliquidLiveAdapter ? buildHlOrderCloid() : randomUUID();
244
+ }
245
+ async function resolveWave9EntryCid(adapter, clientOrderId, symbol) {
246
+ if (!adapter.resolveOrderByClientId) {
247
+ return { status: 'unknown', detail: 'deterministic client-order resolver is unavailable' };
248
+ }
249
+ try {
250
+ return await adapter.resolveOrderByClientId(clientOrderId, symbol);
251
+ }
252
+ catch (err) {
253
+ return { status: 'unknown', detail: `client-order resolution threw: ${formatError(err)}` };
254
+ }
255
+ }
256
+ function flattenRecoveryOutcome(flatten, context, durableOpenEstablished) {
257
+ if (flatten.confirmedFlat && !flatten.terminalPersistenceError) {
258
+ return {
259
+ status: 'terminal',
260
+ detail: flatten.closeAttempted
261
+ ? `${context}; emergency close reached stable flat and durable ownership is CLOSED`
262
+ : `${context}; exchange was already stably flat and durable ownership is CLOSED`,
263
+ };
264
+ }
265
+ if (flatten.confirmedFlat) {
266
+ return {
267
+ status: 'manual_intervention',
268
+ detail: `${context}; stable flat was established, but durable CLOSED persistence failed: ` +
269
+ `${flatten.terminalPersistenceError}`,
270
+ };
271
+ }
272
+ if (flatten.residualProtection?.status === 'protected') {
273
+ if (!durableOpenEstablished) {
274
+ return {
275
+ status: 'manual_intervention',
276
+ detail: `${context}; the residual has a proven frozen native stop ` +
277
+ `${flatten.residualProtection.stopPrice}, but durable OPEN ownership is unavailable. ` +
278
+ 'IMMEDIATE MANUAL INTERVENTION REQUIRED',
279
+ };
280
+ }
281
+ return {
282
+ status: 'protected',
283
+ detail: `${context}; emergency close left a residual, whose exact frozen native stop ` +
284
+ `${flatten.residualProtection.stopPrice} is proven live`,
285
+ };
286
+ }
287
+ return {
288
+ status: 'manual_intervention',
289
+ detail: `${context}; ${flatten.closeAttempted
290
+ ? 'emergency close did not establish stable flat'
291
+ : 'symbol-wide flatten was not authorized by the exposure identity fence'} ` +
292
+ `(${flatten.confirmationDetail}). ` +
293
+ `${flatten.residualProtection?.detail ?? 'IMMEDIATE MANUAL INTERVENTION REQUIRED'}`,
294
+ };
295
+ }
296
+ async function recoverWave9LiveSubmitThrow(input) {
297
+ const recoverCandidateExposure = async (foundOrder, recoveryQuantity, observation, context, entryTerminalProven) => {
298
+ if (!entryTerminalProven) {
299
+ let openPersistenceError;
300
+ try {
301
+ // Establish ownership without freezing an underreported fill while
302
+ // the entry order may still add to the position.
303
+ await input.ledger.markOpen(input.candidateId, {
304
+ ...(foundOrder ? { exchangeOrderId: foundOrder.id } : {}),
305
+ });
306
+ }
307
+ catch (err) {
308
+ openPersistenceError = formatError(err);
309
+ }
310
+ let stopConfirmed = false;
311
+ try {
312
+ stopConfirmed = await input.confirmStop({
313
+ candidateId: input.candidateId,
314
+ symbol: input.symbol,
315
+ entryClientOrderId: input.clientOrderId,
316
+ entrySide: input.side,
317
+ stopPrice: input.stopPrice,
318
+ filledQuantity: recoveryQuantity,
319
+ });
320
+ }
321
+ catch {
322
+ stopConfirmed = false;
323
+ }
324
+ return {
325
+ status: 'manual_intervention',
326
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: ${context}; the deterministic entry order ` +
327
+ `is not proven terminal, so its final filled quantity is unknown and emergency flatten ` +
328
+ `cannot safely claim CLOSED. ${stopConfirmed
329
+ ? 'A candidate-bound close-all stop is currently proven live.'
330
+ : 'Exact native stop protection is unconfirmed.'}` +
331
+ `${openPersistenceError ? ` Durable OPEN persistence failed (${openPersistenceError}).` : ''}`,
332
+ };
333
+ }
334
+ const persistTerminalOpen = async () => {
335
+ if (recoveryQuantity > input.authorizedQuantity) {
336
+ return `final fill ${recoveryQuantity} exceeds authorized quantity ${input.authorizedQuantity}`;
337
+ }
338
+ const entryPrice = foundOrder
339
+ ? confirmedWave9EntryPrice(foundOrder, input.side, input.stopPrice)
340
+ : undefined;
341
+ try {
342
+ await input.ledger.markOpen(input.candidateId, {
343
+ filledQuantity: recoveryQuantity,
344
+ ...(foundOrder ? { exchangeOrderId: foundOrder.id } : {}),
345
+ ...(entryPrice !== undefined ? { entryPrice } : {}),
346
+ });
347
+ return undefined;
348
+ }
349
+ catch (err) {
350
+ const markOpenError = formatError(err);
351
+ if (foundOrder
352
+ && typeof input.ledger.reconcileOpenFillFromExactOrder === 'function') {
353
+ try {
354
+ await input.ledger.reconcileOpenFillFromExactOrder(input.candidateId, {
355
+ filledQuantity: recoveryQuantity,
356
+ exchangeOrderId: foundOrder.id,
357
+ ...(entryPrice !== undefined ? { entryPrice } : {}),
358
+ });
359
+ return undefined;
360
+ }
361
+ catch (reconcileError) {
362
+ return `${markOpenError}; exact terminal fill reconciliation failed: ${formatError(reconcileError)}`;
363
+ }
364
+ }
365
+ return markOpenError;
366
+ }
367
+ };
368
+ const openPersistenceError = await persistTerminalOpen();
369
+ const maximumRecoverableQuantity = Math.min(recoveryQuantity, input.authorizedQuantity);
370
+ let currentObservation = observation;
371
+ if (currentObservation.status !== 'visible') {
372
+ const flat = await confirmWave9LivePositionFlat(input.adapter, input.symbol);
373
+ if (flat.confirmed) {
374
+ if (openPersistenceError) {
375
+ return {
376
+ status: 'manual_intervention',
377
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: exact terminal fill and stable flatness ` +
378
+ `were proven, but durable OPEN persistence failed (${openPersistenceError})`,
379
+ };
380
+ }
381
+ try {
382
+ await input.ledger.markClosed(input.candidateId);
383
+ return {
384
+ status: 'terminal',
385
+ detail: `${context}; exact terminal fill is no longer present and exchange flatness ` +
386
+ `remained stable (${flat.detail}); durable ownership is CLOSED`,
387
+ };
388
+ }
389
+ catch (err) {
390
+ return {
391
+ status: 'manual_intervention',
392
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: exact terminal fill and stable flatness ` +
393
+ `were proven, but CLOSED persistence failed (${formatError(err)})`,
394
+ };
395
+ }
396
+ }
397
+ // Visibility can lag the exact order lookup. Re-read only as positive
398
+ // identity evidence after the stable-flat window; never interpret a
399
+ // failed/unknown flat proof as permission to close a symbol.
400
+ currentObservation = await observeWave9LiveResidual(input.adapter, input.symbol, input.side);
401
+ }
402
+ const quantity = currentObservation.status === 'visible'
403
+ ? currentObservation.quantity
404
+ : undefined;
405
+ if (quantity === undefined
406
+ || !Number.isFinite(quantity)
407
+ || quantity <= 0
408
+ || quantity > maximumRecoverableQuantity) {
409
+ return {
410
+ status: 'manual_intervention',
411
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: exact terminal entry fill is proven, but ` +
412
+ `current same-symbol exposure is not safely candidate-bound (${currentObservation.detail}; ` +
413
+ `maximum attributable quantity ${maximumRecoverableQuantity}). Refusing native-stop ` +
414
+ `replacement or symbol-wide flatten.${openPersistenceError
415
+ ? ` Durable OPEN persistence also failed (${openPersistenceError}).`
416
+ : ' Durable ownership remains OPEN.'}`,
417
+ };
418
+ }
419
+ if (!openPersistenceError) {
420
+ let stopConfirmed = false;
421
+ try {
422
+ stopConfirmed = await input.confirmStop({
423
+ candidateId: input.candidateId,
424
+ symbol: input.symbol,
425
+ entryClientOrderId: input.clientOrderId,
426
+ entrySide: input.side,
427
+ stopPrice: input.stopPrice,
428
+ filledQuantity: recoveryQuantity,
429
+ });
430
+ }
431
+ catch {
432
+ stopConfirmed = false;
433
+ }
434
+ if (stopConfirmed) {
435
+ return {
436
+ status: 'protected',
437
+ detail: `${context}; the deterministic entry was recovered OPEN with an exact proven native stop`,
438
+ ...(foundOrder ? { order: foundOrder } : {}),
439
+ };
440
+ }
441
+ }
442
+ // A positive exact fill establishes durable ownership, but does not by
443
+ // itself authorize a symbol-wide mutation. The helper re-observes current
444
+ // exposure and runs close/re-protection only when side and quantity still
445
+ // prove that the residual can belong to this candidate.
446
+ const flatten = await flattenFailedWave9LiveEntry(input.adapter, input.ledger, input.symbol, input.candidateId, {
447
+ side: input.side,
448
+ maximumQuantity: Math.min(recoveryQuantity, input.authorizedQuantity),
449
+ }, input.residualProtector);
450
+ const outcome = flattenRecoveryOutcome(flatten, openPersistenceError
451
+ ? `${context}; OPEN persistence failed (${openPersistenceError}); ${observation.detail}`
452
+ : `${context}; exact stop was unavailable; ${observation.detail}`, openPersistenceError === undefined);
453
+ return foundOrder && outcome.status === 'protected'
454
+ ? { ...outcome, order: foundOrder }
455
+ : outcome;
456
+ };
457
+ const exactStopProven = async (quantity) => {
458
+ let stopConfirmed = false;
459
+ try {
460
+ stopConfirmed = await input.confirmStop({
461
+ candidateId: input.candidateId,
462
+ symbol: input.symbol,
463
+ entryClientOrderId: input.clientOrderId,
464
+ entrySide: input.side,
465
+ stopPrice: input.stopPrice,
466
+ filledQuantity: quantity,
467
+ });
468
+ }
469
+ catch {
470
+ stopConfirmed = false;
471
+ }
472
+ return stopConfirmed;
473
+ };
474
+ const terminalizeOpenEntry = async (order) => {
475
+ try {
476
+ await input.adapter.cancelOrder(order.id, input.symbol);
477
+ }
478
+ catch (err) {
479
+ return { error: `exact open-entry cancellation failed: ${formatError(err)}` };
480
+ }
481
+ const afterCancel = await resolveWave9EntryCid(input.adapter, input.clientOrderId, input.symbol);
482
+ if (afterCancel.status === 'confirmed_absent') {
483
+ return order.filled === 0
484
+ ? { confirmedAbsent: true }
485
+ : { error: 'positive-fill entry disappeared after cancellation; final fill is unknown' };
486
+ }
487
+ const resolved = candidateBoundFoundOrder(afterCancel, input.clientOrderId, input.symbol, input.side);
488
+ if (!resolved) {
489
+ return {
490
+ error: afterCancel.status === 'unknown'
491
+ ? `post-cancel entry state is unknown (${afterCancel.detail})`
492
+ : 'post-cancel entry identity is mismatched',
493
+ };
494
+ }
495
+ return resolved.status === 'open'
496
+ ? { error: 'exact entry order remains open after cancellation' }
497
+ : { order: resolved };
498
+ };
499
+ const acknowledgedOrder = input.acknowledgedOrder
500
+ ? candidateBoundFoundOrder({ status: 'found', order: input.acknowledgedOrder }, input.clientOrderId, input.symbol, input.side)
501
+ : undefined;
502
+ let resolution = acknowledgedOrder
503
+ ? { status: 'found', order: acknowledgedOrder }
504
+ : await resolveWave9EntryCid(input.adapter, input.clientOrderId, input.symbol);
505
+ let foundOrder = candidateBoundFoundOrder(resolution, input.clientOrderId, input.symbol, input.side);
506
+ if (resolution.status === 'found' && !foundOrder) {
507
+ return {
508
+ status: 'manual_intervention',
509
+ detail: 'IMMEDIATE MANUAL INTERVENTION REQUIRED: client-order lookup returned mismatched candidate identity',
510
+ };
511
+ }
512
+ let residual = await observeWave9LiveResidual(input.adapter, input.symbol, input.side);
513
+ let foundFilled = foundOrder
514
+ && Number.isFinite(foundOrder.filled)
515
+ && foundOrder.filled > 0
516
+ ? foundOrder.filled
517
+ : undefined;
518
+ if (foundFilled !== undefined) {
519
+ if (foundOrder.status === 'open') {
520
+ const terminalized = await terminalizeOpenEntry(foundOrder);
521
+ if (!terminalized.order || terminalized.order.filled <= 0) {
522
+ try {
523
+ await input.ledger.markOpen(input.candidateId, {
524
+ exchangeOrderId: foundOrder.id,
525
+ });
526
+ }
527
+ catch {
528
+ // The manual outcome remains load-bearing even if persistence fails.
529
+ }
530
+ return {
531
+ status: 'manual_intervention',
532
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: positive partial fill has an unproven ` +
533
+ `final quantity (${terminalized.error ?? 'post-cancel identity unavailable'}); ` +
534
+ 'durable ownership remains OPEN without a frozen filled quantity',
535
+ };
536
+ }
537
+ foundOrder = terminalized.order;
538
+ foundFilled = terminalized.order.filled;
539
+ }
540
+ return recoverCandidateExposure(foundOrder, foundFilled, residual, `submit threw (${formatError(input.submitError)}) and the exact CID shows a positive fill`, true);
541
+ }
542
+ if (resolution.status === 'unknown' && residual.status === 'visible') {
543
+ return recoverCandidateExposure(undefined, residual.quantity, residual, `submit threw and CID resolution is unknown with a same-side residual`, false);
544
+ }
545
+ // A proven candidate-bound close-all stop is itself strong evidence that
546
+ // LiveAdapter progressed past submission even if position visibility lags.
547
+ if (resolution.status === 'unknown'
548
+ && await exactStopProven(input.authorizedQuantity)) {
549
+ return recoverCandidateExposure(undefined, input.authorizedQuantity, residual, 'submit acknowledgement is unknown, but its exact candidate-bound native stop is live', false);
550
+ }
551
+ if (foundOrder?.status === 'open' && foundOrder.filled === 0) {
552
+ const terminalized = await terminalizeOpenEntry(foundOrder);
553
+ if (terminalized.error) {
554
+ return {
555
+ status: 'manual_intervention',
556
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: the deterministic zero-fill entry remains ` +
557
+ `nonterminal (${terminalized.error})`,
558
+ };
559
+ }
560
+ if (terminalized.confirmedAbsent) {
561
+ resolution = { status: 'confirmed_absent' };
562
+ foundOrder = undefined;
563
+ }
564
+ else if (terminalized.order) {
565
+ resolution = { status: 'found', order: terminalized.order };
566
+ foundOrder = terminalized.order;
567
+ }
568
+ }
569
+ const flat = await confirmWave9LivePositionFlat(input.adapter, input.symbol);
570
+ let finalResolution = await resolveWave9EntryCid(input.adapter, input.clientOrderId, input.symbol);
571
+ let finalOrder = candidateBoundFoundOrder(finalResolution, input.clientOrderId, input.symbol, input.side);
572
+ if (finalResolution.status === 'found' && !finalOrder) {
573
+ return {
574
+ status: 'manual_intervention',
575
+ detail: 'IMMEDIATE MANUAL INTERVENTION REQUIRED: final client-order lookup returned mismatched candidate identity',
576
+ };
577
+ }
578
+ if (finalOrder?.status === 'open') {
579
+ const openOrder = finalOrder;
580
+ const terminalized = await terminalizeOpenEntry(openOrder);
581
+ if (terminalized.error || (!terminalized.order && !terminalized.confirmedAbsent)) {
582
+ if (openOrder.filled > 0) {
583
+ try {
584
+ await input.ledger.markOpen(input.candidateId, {
585
+ exchangeOrderId: openOrder.id,
586
+ });
587
+ }
588
+ catch {
589
+ // The explicit manual outcome below remains authoritative.
590
+ }
591
+ }
592
+ return {
593
+ status: 'manual_intervention',
594
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: final exact entry order is nonterminal ` +
595
+ `(${terminalized.error ?? 'post-cancel state unavailable'})`,
596
+ };
597
+ }
598
+ if (terminalized.confirmedAbsent) {
599
+ finalResolution = { status: 'confirmed_absent' };
600
+ finalOrder = undefined;
601
+ }
602
+ else {
603
+ finalOrder = terminalized.order;
604
+ finalResolution = { status: 'found', order: terminalized.order };
605
+ }
606
+ }
607
+ residual = await observeWave9LiveResidual(input.adapter, input.symbol, input.side);
608
+ if (finalOrder && Number.isFinite(finalOrder.filled) && finalOrder.filled > 0) {
609
+ return recoverCandidateExposure(finalOrder, finalOrder.filled, residual, 'the exact CID resolved to a positive fill after the settlement window', true);
610
+ }
611
+ if (finalResolution.status === 'unknown' && residual.status === 'visible') {
612
+ return recoverCandidateExposure(undefined, residual.quantity, residual, 'CID resolution remained unknown and a residual appeared during the settlement window', false);
613
+ }
614
+ if (finalResolution.status === 'unknown'
615
+ && await exactStopProven(input.authorizedQuantity)) {
616
+ return recoverCandidateExposure(undefined, input.authorizedQuantity, residual, 'CID resolution remained unknown, but an exact candidate-bound native stop appeared', false);
617
+ }
618
+ const terminalOrder = finalOrder?.status === 'canceled' && finalOrder.filled === 0;
619
+ let terminalFlat = flat;
620
+ if (finalResolution.status === 'confirmed_absent' || terminalOrder) {
621
+ // Bind terminalization to a fresh stable-flat window *after* exact CID
622
+ // terminal proof. The earlier settlement window can go stale while the
623
+ // final lookup/cancellation runs.
624
+ terminalFlat = await confirmWave9LivePositionFlat(input.adapter, input.symbol);
625
+ }
626
+ if (terminalFlat.confirmed
627
+ && (finalResolution.status === 'confirmed_absent' || terminalOrder)) {
628
+ try {
629
+ await input.ledger.markClosed(input.candidateId);
630
+ return {
631
+ status: 'terminal',
632
+ detail: `submit threw (${formatError(input.submitError)}), but the exact client order is ` +
633
+ `${terminalOrder ? 'canceled with zero fill' : 'confirmed absent'} and the exchange ` +
634
+ `remained stably flat`,
635
+ };
636
+ }
637
+ catch (err) {
638
+ return {
639
+ status: 'manual_intervention',
640
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: entry absence and stable flat were proven, ` +
641
+ `but CLOSED persistence failed (${formatError(err)})`,
642
+ };
643
+ }
644
+ }
645
+ return {
646
+ status: 'manual_intervention',
647
+ detail: `IMMEDIATE MANUAL INTERVENTION REQUIRED: submit outcome remains ambiguous; client-order ` +
648
+ `resolution is ${finalResolution.status} and stable flat is ${terminalFlat.confirmed
649
+ ? 'present but insufficient without deterministic absence'
650
+ : `unconfirmed (${terminalFlat.detail})`}. The entry capability is consumed and must not be retried`,
651
+ };
652
+ }
653
+ /** Build a PortfolioSnapshot from current simulator state (paper mode). */
654
+ function buildPortfolioSnapshotFromSimulator(simulator) {
655
+ const state = simulator.getState();
656
+ const quoteCurrency = state.config.quoteCurrency;
657
+ const walletBal = state.wallet[quoteCurrency];
658
+ return {
659
+ positions: state.positions.map(p => {
660
+ const ticker = simulator.getLastTicker(p.symbol);
661
+ return {
662
+ symbol: p.symbol,
663
+ side: p.side,
664
+ quantity: p.quantity,
665
+ entryPrice: p.entryPrice,
666
+ markPrice: ticker?.last ?? p.entryPrice,
667
+ };
668
+ }),
669
+ walletTotal: walletBal?.total ?? 0,
670
+ walletAvailable: walletBal?.available ?? 0,
671
+ sessionStartNav: simulator.getSessionStartNav(),
672
+ equity: simulator.computeEquity(),
673
+ isLive: false,
674
+ };
675
+ }
676
+ /** Build a PortfolioSnapshot from adapter (live mode). */
677
+ async function buildPortfolioSnapshotFromAdapter(adapter) {
678
+ // Null-honest balance read where the adapter offers one (LiveAdapter) —
679
+ // getBalance() collapses a FAILED fetch to a phantom-zero object, which
680
+ // reads as walletTotal=0 against the real sessionStartNav → ~−100%
681
+ // drawdown → RED zone rejects the entry for the wrong reason (and sizing
682
+ // runs against walletAvailable=0).
683
+ const balanceRead = adapter.getBalanceOrNull?.() ?? adapter.getBalance();
684
+ const [balance, positions] = await Promise.all([
685
+ balanceRead,
686
+ adapter.getPositionsOrNull(),
687
+ ]);
688
+ // null = positions fetch FAILED (429 / weight-paced / transient). If we
689
+ // collapsed that to [], existing open positions become invisible to the
690
+ // pre-trade risk check below — gross exposure / max-open-positions / net
691
+ // tilt would all be under-counted and a trade that should be blocked could
692
+ // pass. Refuse to size off a phantom-flat account; abort so the agent
693
+ // retries next heartbeat. Same null≠empty contract as the close/flatten path.
694
+ if (positions === null) {
695
+ throw new Error('Cannot build portfolio snapshot — position fetch failed (exchange data ' +
696
+ 'unavailable, likely Binance 429 or transient). Order NOT placed; retry next heartbeat.');
697
+ }
698
+ // Same contract for the balance leg: null = fetch failed, NOT a zero wallet.
699
+ if (balance === null) {
700
+ throw new Error('Cannot build portfolio snapshot — balance fetch failed (exchange data ' +
701
+ 'unavailable, likely Binance 429 or transient). Order NOT placed; retry next heartbeat.');
702
+ }
703
+ const walletTotal = getQuoteWalletBalance(balance);
704
+ // Use the session-start NAV captured once at initialization, not the current balance.
705
+ // If not available (shouldn't happen after init), fall back to current balance.
706
+ const sessionNav = adapter.getSessionStartNav?.() ?? walletTotal;
707
+ // Live Binance wallet already includes margin; equity = wallet + unrealized P&L.
708
+ let liveEquity = walletTotal;
709
+ for (const p of positions) {
710
+ const mark = p.markPrice ?? p.entryPrice;
711
+ const pnl = p.side === 'long'
712
+ ? (mark - p.entryPrice) * p.contracts
713
+ : (p.entryPrice - mark) * p.contracts;
714
+ liveEquity += pnl;
715
+ }
716
+ return {
717
+ positions: positions.map(p => ({
718
+ symbol: p.symbol,
719
+ side: p.side,
720
+ quantity: p.contracts,
721
+ entryPrice: p.entryPrice,
722
+ markPrice: p.markPrice,
723
+ })),
724
+ walletTotal,
725
+ walletAvailable: getQuoteBalance(balance.free),
726
+ sessionStartNav: sessionNav,
727
+ equity: liveEquity,
728
+ isLive: true,
729
+ };
730
+ }
731
+ export async function createOrderTool(args, deps) {
732
+ const wave9Claimed = isWave9ManagedPosition({
733
+ setupType: args.setup_type,
734
+ missionId: args.mission_id,
735
+ })
736
+ || args.wave9_admission_token !== undefined
737
+ || args.candidate_id !== undefined;
738
+ if (wave9Claimed && deps.wave9VenueCheck?.() !== 'binance') {
739
+ return { error: 'Wave 9 order refused: execution is pinned to Binance USD-M futures.' };
740
+ }
741
+ if (wave9Claimed && !deps.operationLock) {
742
+ return { error: 'Wave 9 order refused: process-local trading operation lock is unavailable.' };
743
+ }
744
+ const initialWave9Mode = wave9Claimed ? currentWave9ExecutionMode(deps) : null;
745
+ // Wave 9 is a dedicated targetless capability path. Never let a generic
746
+ // order impersonate it, and require the adapter + mutable runtime mode to
747
+ // agree exactly before accepting either PAPER or LIVE execution.
748
+ if (wave9Claimed) {
749
+ if (args.setup_type !== WAVE9_BUNDLE_SETUP_TYPE) {
750
+ return { error: `Wave 9 order refused: setup_type must be exactly ${WAVE9_BUNDLE_SETUP_TYPE}.` };
751
+ }
752
+ if (!initialWave9Mode) {
753
+ return {
754
+ error: 'Wave 9 order refused: capability execution requires an exact PAPER/paper-adapter ' +
755
+ 'or LIVE/live-adapter runtime match.',
756
+ };
757
+ }
758
+ if ((deps.approvalMode ?? 'off') !== 'off') {
759
+ return { error: 'Wave 9 order refused: approval mode must be off for immediate mechanical execution.' };
760
+ }
761
+ if (!deps.wave9AdmissionGuard || !args.wave9_admission_token || !args.candidate_id || !args.mission_id) {
762
+ return { error: 'Wave 9 order refused: a dedicated short-lived admission token and exact candidate/mission identity are required.' };
763
+ }
764
+ if (args.type.toLowerCase() !== 'market' || args.price !== undefined || args.target_price !== undefined) {
765
+ return { error: 'Wave 9 order refused: the frozen policy requires a targetless market order with no limit price.' };
766
+ }
767
+ const canonical = args.symbol.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
768
+ if (!canonical.endsWith('USDT') || canonical.length <= 4) {
769
+ return { error: `Wave 9 order refused: unsupported execution symbol ${args.symbol}.` };
770
+ }
771
+ if (args.stopPrice === undefined || !Number.isFinite(args.stopPrice) || args.stopPrice <= 0) {
772
+ return { error: 'Wave 9 order refused: a positive finite initial stop is required.' };
773
+ }
774
+ if (initialWave9Mode === 'LIVE') {
775
+ if (!deps.captureWave9AccountFingerprint
776
+ || !deps.confirmWave9LiveStop
777
+ || !deps.wave9LiveLedger) {
778
+ return {
779
+ error: 'Wave 9 live order refused: authoritative live account capture, native-stop confirmation, ' +
780
+ 'and the durable execution ledger are required.',
781
+ };
782
+ }
783
+ if (!bracketsEnabled(loadBracketMode())) {
784
+ return { error: 'Wave 9 live order refused: exchange-native bracket protection is not enabled.' };
785
+ }
786
+ }
787
+ // Persist one unified USD-M key regardless of whether an older agent
788
+ // echoes BTCUSDT or BTC/USDT. Guard matching remains canonical.
789
+ args = { ...args, symbol: `${canonical.slice(0, -4)}/USDT:USDT` };
790
+ }
791
+ // ---- Readiness gate (live mode only) ----
792
+ if (deps.adapter.readiness !== 'READY') {
793
+ return { error: `Trading blocked: adapter is ${deps.adapter.readiness}. Wait for initialization to complete.` };
794
+ }
795
+ // ---- Assessment-schema gate (SKILL.md v2.10.0 — live mode only) ----
796
+ // In live mode the agent must include thesis + setup_type + regime +
797
+ // regime_confidence + scorecard_verdict + confluence_score. This is the
798
+ // same data the heartbeat reassessment uses to detect scorecard reversal /
799
+ // regime flip — if the agent can't produce it at entry, it can't produce
800
+ // it mid-trade either.
801
+ const gateCheck = validateCreateOrder(args, deps.adapter.isLive && !wave9Claimed);
802
+ if (!gateCheck.ok) {
803
+ return { error: gateCheck.error ?? 'Invalid create_order arguments.' };
804
+ }
805
+ // Validate side
806
+ const side = args.side.toLowerCase();
807
+ if (side !== 'buy' && side !== 'sell') {
808
+ return { error: `Invalid side: ${args.side}. Must be 'buy' or 'sell'.` };
809
+ }
810
+ // Validate type
811
+ const type = args.type.toLowerCase();
812
+ if (type !== 'market' && type !== 'limit') {
813
+ return { error: `Invalid type: ${args.type}. Must be 'market' or 'limit'.` };
814
+ }
815
+ // Validate amount
816
+ if (!args.amount || args.amount <= 0) {
817
+ return { error: 'Amount must be a positive number.' };
818
+ }
819
+ // Validate price for limit orders
820
+ if (type === 'limit' && (!args.price || args.price <= 0)) {
821
+ return { error: 'Limit orders require a positive price.' };
822
+ }
823
+ if (args.mission_id !== undefined
824
+ && (typeof args.mission_id !== 'string'
825
+ || !/^[A-Za-z0-9_-]{1,128}$/.test(args.mission_id))) {
826
+ return { error: 'mission_id must be 1-128 letters, numbers, underscores, or hyphens.' };
827
+ }
828
+ if (!wave9Claimed
829
+ && deps.adapter.isLive
830
+ && deps.checkWave9LiveSymbolOwnership
831
+ && !deps.operationLock) {
832
+ return {
833
+ error: 'Generic live create_order refused: the shared trading operation lock is required ' +
834
+ 'while Wave 9 LIVE ownership checks are enabled.',
835
+ };
836
+ }
837
+ if (!wave9Claimed && !deps.adapter.isLive) {
838
+ try {
839
+ deps.refreshPaperState?.();
840
+ const positions = await deps.adapter.getPositionsOrNull();
841
+ if (positions === null) {
842
+ return { error: 'create_order refused: paper position state unavailable for Wave 9 isolation check.' };
843
+ }
844
+ if (positions.some(isWave9ManagedPosition)) {
845
+ return {
846
+ error: 'Generic create_order refused: the paper account contains an atomic Wave 9 position. ' +
847
+ 'The Wave 9 forward book must remain isolated until all of its positions are flat.',
848
+ };
849
+ }
850
+ }
851
+ catch (err) {
852
+ return { error: `create_order refused: Wave 9 isolation check failed (${formatError(err)}).` };
853
+ }
854
+ }
855
+ // In paper mode, fetch latest ticker + order book for realistic fills
856
+ let ticker = null;
857
+ if (!deps.adapter.isLive) {
858
+ const paperDeps = { binanceApi: deps.binanceApi, simulator: deps.adapter.getSimulator() };
859
+ const [priceResult] = await Promise.all([
860
+ fetchCurrentPrice(args.symbol, paperDeps),
861
+ fetchOrderBook(args.symbol, paperDeps),
862
+ ]);
863
+ if (isError(priceResult))
864
+ return priceResult;
865
+ ticker = priceResult;
866
+ }
867
+ else {
868
+ // Live mode: fetch ticker for risk check reference price
869
+ const lastPrice = await deps.adapter.getLastPrice(args.symbol);
870
+ if (lastPrice != null) {
871
+ ticker = {
872
+ symbol: args.symbol, last: lastPrice,
873
+ bid: lastPrice, ask: lastPrice, baseVolume: 0, quoteVolume: 0,
874
+ change: 0, percentage: 0, timestamp: Date.now(), datetime: new Date().toISOString(),
875
+ };
876
+ }
877
+ }
878
+ if (!ticker) {
879
+ return { error: `Failed to fetch current price for ${args.symbol}` };
880
+ }
881
+ // Once market data exists, consume the capability before any runtime
882
+ // geometry or generic-gate decision. A delayed stop that is now on the
883
+ // wrong side is an execution divergence and must latch the daily event.
884
+ let wave9Lease;
885
+ if (wave9Claimed) {
886
+ try {
887
+ wave9Lease = await deps.operationLock.withAccountLock(`wave9-begin-create:${args.candidate_id ?? args.symbol}`, async () => {
888
+ if (currentWave9ExecutionMode(deps) !== initialWave9Mode) {
889
+ throw new Error('adapter or trading mode changed before Wave 9 admission began');
890
+ }
891
+ let accountFingerprint;
892
+ if (initialWave9Mode === 'LIVE') {
893
+ const ownership = deps.checkWave9LiveSymbolOwnership
894
+ ? await deps.checkWave9LiveSymbolOwnership(args.symbol)
895
+ : await inspectWave9LiveSymbolOwnership(deps.adapter, deps.wave9LiveLedger, args.symbol);
896
+ if (ownership.status !== 'unowned'
897
+ || ownership.reason === 'authoritative_symbol_flat') {
898
+ throw new Error(`candidate-symbol ownership must be refreshed after ` +
899
+ `${ownership.status}:${ownership.reason}`);
900
+ }
901
+ accountFingerprint = (await deps.captureWave9AccountFingerprint()).fingerprint;
902
+ }
903
+ else {
904
+ deps.refreshPaperState?.();
905
+ deps.adapter.getSimulator().getSessionStartNav();
906
+ accountFingerprint = captureWave9PaperAccountSnapshot(deps.adapter).fingerprint;
907
+ }
908
+ return deps.wave9AdmissionGuard.beginEntry({
909
+ token: args.wave9_admission_token,
910
+ candidateId: args.candidate_id,
911
+ missionId: args.mission_id,
912
+ symbol: args.symbol,
913
+ side,
914
+ type,
915
+ quantity: args.amount,
916
+ stopPrice: args.stopPrice,
917
+ setupType: args.setup_type,
918
+ accountFingerprint,
919
+ tradingMode: initialWave9Mode,
920
+ });
921
+ });
922
+ }
923
+ catch (err) {
924
+ return { error: `Wave 9 admission refused: ${formatError(err)}` };
925
+ }
926
+ }
927
+ const rejectWave9Divergence = (message, code) => {
928
+ if (wave9Lease) {
929
+ deps.wave9AdmissionGuard.abort(wave9Lease, code);
930
+ wave9Lease = undefined;
931
+ }
932
+ return { error: `${wave9Claimed ? 'Wave 9 runtime divergence; daily event latched. ' : ''}${message}` };
933
+ };
934
+ // From beginEntry onward, every unexpected failure must release the
935
+ // in-flight capability. Explicit geometry/risk divergences still use the
936
+ // latching helper above; infrastructure/pure-computation throws are
937
+ // retryable because no exchange mutation has been attempted.
938
+ try {
939
+ // ---- Pre-trade risk gate ----
940
+ const orderPrice = type === 'limit' ? args.price : ticker.last;
941
+ if (wave9Claimed && args.stopPrice != null) {
942
+ if (!Number.isFinite(args.stopPrice) || args.stopPrice <= 0) {
943
+ return rejectWave9Divergence('stopPrice must be a positive finite number.', 'invalid_stop_price');
944
+ }
945
+ if ((side === 'buy' && args.stopPrice >= orderPrice) ||
946
+ (side === 'sell' && args.stopPrice <= orderPrice)) {
947
+ return rejectWave9Divergence(`stopPrice is on the wrong side of entry ${orderPrice} for a ${side} order.`, 'actual_market_stop_geometry_diverged');
948
+ }
949
+ }
950
+ // ---- Generic-path protective geometry gate (issue #200) ----
951
+ // The wrong-side check above ran ONLY for wave9 orders; a generic entry with
952
+ // its stop/invalidation on the profit side passed validation and was closed
953
+ // by the stop-watcher seconds later (7 sub-5-minute kills on the 2026-07 HL
954
+ // soak — one spawned the −$117.86 journal fabrication of issue #199).
955
+ if (!wave9Claimed) {
956
+ const geometryError = validateProtectiveGeometry({
957
+ side: side,
958
+ refPrice: orderPrice,
959
+ stopPrice: args.stopPrice,
960
+ invalidationPrice: args.invalidation_price,
961
+ targetPrice: args.target_price,
962
+ });
963
+ if (geometryError) {
964
+ return { error: `create_order rejected (protective geometry): ${geometryError}` };
965
+ }
966
+ }
967
+ const proposed = {
968
+ symbol: args.symbol,
969
+ side: side,
970
+ type: type,
971
+ amount: args.amount,
972
+ price: orderPrice,
973
+ stopPrice: args.stopPrice,
974
+ targetPrice: args.target_price,
975
+ };
976
+ // Build portfolio snapshot based on mode
977
+ let portfolio;
978
+ let consecutiveLosses = 0;
979
+ if (!deps.adapter.isLive) {
980
+ const simulator = deps.adapter.getSimulator();
981
+ portfolio = buildPortfolioSnapshotFromSimulator(simulator);
982
+ consecutiveLosses = computeConsecutiveLosses(simulator.getState().tradeHistory);
983
+ }
984
+ else {
985
+ portfolio = await buildPortfolioSnapshotFromAdapter(deps.adapter);
986
+ // In live mode, consecutive losses would come from intelligence DB — use 0 for now
987
+ }
988
+ // Bracket enforcement only applies in live mode when the feature is enabled.
989
+ // Paper mode uses the stop-watcher and doesn't care about these flags.
990
+ let bracketEnforcement;
991
+ if (deps.adapter.isLive && bracketsEnabled(loadBracketMode())) {
992
+ bracketEnforcement = wave9Claimed
993
+ ? { requireStopLoss: true, requireTakeProfit: false }
994
+ : loadBracketRequirements();
995
+ }
996
+ const riskCheck = preTradeRiskCheck(proposed, portfolio, getDefaultPreTradeLimits(), {
997
+ volFactor: !deps.adapter.isLive ? deps.adapter.getSimulator().getVolFactor() : 1.0,
998
+ consecutiveLosses,
999
+ bracketEnforcement,
1000
+ });
1001
+ if (!riskCheck.allowed) {
1002
+ const reasons = riskCheck.violations.map(v => v.message).join('; ');
1003
+ return rejectWave9Divergence(`Order REJECTED by risk gate [${riskCheck.drawdownZone} zone]: ${reasons}`, `generic_pretrade_divergence:${riskCheck.violations.map((v) => v.rule).join(',')}`);
1004
+ }
1005
+ // Build position metadata from optional args (only if any metadata provided)
1006
+ // Sanitize numeric metadata — reject non-finite values, clamp ranges
1007
+ const num = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
1008
+ const clamp = (v, min, max) => {
1009
+ const n = num(v);
1010
+ return n != null ? Math.max(min, Math.min(max, n)) : undefined;
1011
+ };
1012
+ const hasMetadata = args.mission_id || args.setup_type || args.thesis || args.target_price != null
1013
+ || args.regime || args.scorecard_verdict || args.confluence_score != null
1014
+ || args.invalidation_price != null || args.realization_rule != null;
1015
+ const metadata = hasMetadata ? {
1016
+ missionId: args.mission_id,
1017
+ setupType: args.setup_type,
1018
+ ...(wave9Claimed ? {
1019
+ candidateId: args.candidate_id,
1020
+ strategy: args.candidate_id.split('|')[2],
1021
+ accruedFundingUsd: 0,
1022
+ fundingThroughTime: args.candidate_id.split('|')[0],
1023
+ } : {}),
1024
+ thesis: args.thesis?.slice(0, 500), // Cap length
1025
+ stopPrice: num(args.stopPrice),
1026
+ targetPrice: num(args.target_price),
1027
+ regime: args.regime,
1028
+ regimeConfidence: clamp(args.regime_confidence, 0, 100),
1029
+ scorecardVerdict: args.scorecard_verdict,
1030
+ confluenceScore: clamp(args.confluence_score, 0, 10),
1031
+ invalidationPrice: num(args.invalidation_price),
1032
+ realizationRule: sanitizeRealizationRule(args.realization_rule),
1033
+ } : undefined;
1034
+ // ---- Approval-mode branches ----
1035
+ // Both shadow and per_trade need full v2.10.0 metadata so the proposal is
1036
+ // operator-meaningful. Metadata is the agent's responsibility; ingest creds
1037
+ // are the operator's. Separate so the error messages can be precise.
1038
+ const argsMetadataComplete = !!(args.thesis &&
1039
+ args.setup_type &&
1040
+ args.target_price != null &&
1041
+ args.stopPrice != null &&
1042
+ args.regime &&
1043
+ typeof args.regime_confidence === 'number' &&
1044
+ args.scorecard_verdict &&
1045
+ typeof args.confluence_score === 'number');
1046
+ const proposalPathWired = !!(deps.proposalManager && deps.userId);
1047
+ const approvalMode = deps.approvalMode ?? 'off';
1048
+ // per_trade mode: propose and EARLY-RETURN. The agent's call doesn't fire;
1049
+ // the operator approves and ProposalDecisionListener fires via this same
1050
+ // tool with proposalManager omitted (which takes the real path below).
1051
+ //
1052
+ // If proposalManager isn't wired (operator set approval.mode=per_trade in
1053
+ // config but ingest creds aren't present), we fall through to the normal
1054
+ // fire path — the startup warning is the operator's signal. We never trap
1055
+ // the agent inside an unfireable per_trade branch.
1056
+ if (approvalMode === 'per_trade' && proposalPathWired) {
1057
+ if (!argsMetadataComplete) {
1058
+ return {
1059
+ error: 'approval.mode=per_trade requires the full v2.10.0 metadata block on every create_order: ' +
1060
+ 'thesis, setup_type, target_price, stopPrice, regime, regime_confidence, ' +
1061
+ 'scorecard_verdict, confluence_score. The operator approves on this metadata.',
1062
+ };
1063
+ }
1064
+ try {
1065
+ const result = deps.proposalManager.propose(deps.userId, {
1066
+ symbol: args.symbol,
1067
+ side: side,
1068
+ orderType: type,
1069
+ size: args.amount,
1070
+ proposedEntry: orderPrice,
1071
+ stopPrice: args.stopPrice,
1072
+ targetPrice: args.target_price,
1073
+ riskUsd: Math.abs(orderPrice - args.stopPrice) * args.amount,
1074
+ thesis: args.thesis,
1075
+ setupType: args.setup_type,
1076
+ regime: args.regime,
1077
+ regimeConfidence: args.regime_confidence,
1078
+ scorecardVerdict: args.scorecard_verdict,
1079
+ confluenceScore: args.confluence_score,
1080
+ ...(args.supersedes_id ? { supersedesId: args.supersedes_id } : {}),
1081
+ // Real, operator-actionable. Default would also resolve to 'real' but
1082
+ // we set it explicitly so the intent reads at the call site.
1083
+ origin: 'real',
1084
+ });
1085
+ return {
1086
+ status: 'pending_approval',
1087
+ proposal_id: result.proposalUuid,
1088
+ hard_expires_at: result.hardExpiresAt.toISOString(),
1089
+ setup_bucket: result.setupBucket,
1090
+ message: `Order awaits operator approval (${result.setupBucket} bucket, expires ` +
1091
+ `${result.hardExpiresAt.toISOString()}). Do not call create_order again ` +
1092
+ `for this setup; the listener will fire on approval. If the operator ` +
1093
+ `requests modifications in chat, call create_order with supersedes_id=` +
1094
+ `<id from the chat-context header>.`,
1095
+ };
1096
+ }
1097
+ catch (err) {
1098
+ return { error: `Proposal submission failed: ${formatError(err)}` };
1099
+ }
1100
+ }
1101
+ // shadow mode: dual-write a telemetry proposal AND continue to the real
1102
+ // fire below. Never blocks the hot path on a propose() failure.
1103
+ if (approvalMode === 'shadow' && proposalPathWired && argsMetadataComplete) {
1104
+ try {
1105
+ deps.proposalManager.propose(deps.userId, {
1106
+ symbol: args.symbol,
1107
+ side: side,
1108
+ orderType: type,
1109
+ size: args.amount,
1110
+ proposedEntry: orderPrice,
1111
+ stopPrice: args.stopPrice,
1112
+ targetPrice: args.target_price,
1113
+ riskUsd: Math.abs(orderPrice - args.stopPrice) * args.amount,
1114
+ thesis: args.thesis,
1115
+ setupType: args.setup_type,
1116
+ regime: args.regime,
1117
+ regimeConfidence: args.regime_confidence,
1118
+ scorecardVerdict: args.scorecard_verdict,
1119
+ confluenceScore: args.confluence_score,
1120
+ ...(args.supersedes_id ? { supersedesId: args.supersedes_id } : {}),
1121
+ // Phase A telemetry — agent's create_order ALSO fires the real order
1122
+ // synchronously below. Hidden from operator UI by origin='shadow'.
1123
+ origin: 'shadow',
1124
+ });
1125
+ }
1126
+ catch (err) {
1127
+ // eslint-disable-next-line no-console
1128
+ console.warn(`[approval-shadow] propose() threw: ${formatError(err)}`);
1129
+ }
1130
+ }
1131
+ try {
1132
+ // Stash the metadata BEFORE submission, keyed by a pre-generated
1133
+ // clientOrderId (fixed 2026-07-07; supersedes the 2026-07-05 post-return
1134
+ // stash). For market orders the user-data WS fill routinely arrives
1135
+ // BEFORE the REST ack resolves — observed on prod (FIL 2026-07-06
1136
+ // 20:28:17: WS capture consumed nothing and journaled metadata=none while
1137
+ // the post-return stash landed milliseconds later). The WS
1138
+ // ORDER_TRADE_UPDATE carries the clientOrderId (`o.c`), so
1139
+ // onWsFillObserved falls back to it when the exchange orderId lookup
1140
+ // misses; promote() below adds the exchange-orderId alias once the REST
1141
+ // ack returns for the normal (WS-after-REST) ordering. If the submission
1142
+ // throws, the unused stash entry simply expires (24h TTL, pruned).
1143
+ let stashCid = wave9Claimed && initialWave9Mode === 'LIVE'
1144
+ ? wave9ClientOrderId(args.candidate_id)
1145
+ : undefined;
1146
+ if (deps.autoCapture?.pendingEntries && metadata) {
1147
+ stashCid ??= mintEntryStashCid(deps.adapter);
1148
+ deps.autoCapture.pendingEntries.put({
1149
+ orderId: stashCid,
1150
+ clientOrderId: stashCid,
1151
+ symbol: args.symbol,
1152
+ side: side,
1153
+ metadata,
1154
+ });
1155
+ }
1156
+ if (wave9Lease) {
1157
+ if (currentWave9ExecutionMode(deps) !== initialWave9Mode) {
1158
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'trading_mode_changed_before_mutation', { retryable: true, latchEvent: false });
1159
+ wave9Lease = undefined;
1160
+ return { error: 'Wave 9 order refused: adapter or trading mode changed before mutation.' };
1161
+ }
1162
+ if (deps.wave9VenueCheck?.() !== 'binance') {
1163
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'execution_venue_changed_before_mutation', { retryable: true, latchEvent: false });
1164
+ wave9Lease = undefined;
1165
+ return { error: 'Wave 9 order refused: execution venue changed away from Binance before mutation.' };
1166
+ }
1167
+ let activation;
1168
+ try {
1169
+ activation = deps.wave9ActivationCheck
1170
+ ? await deps.wave9ActivationCheck()
1171
+ : { available: false, active: false };
1172
+ }
1173
+ catch {
1174
+ activation = { available: false, active: false };
1175
+ }
1176
+ if (!activation.available || !activation.active) {
1177
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'activation_unavailable_or_inactive_before_mutation', { retryable: true, latchEvent: false });
1178
+ wave9Lease = undefined;
1179
+ return { error: 'Wave 9 order refused: strategy was toggled off or activation could not be re-verified.' };
1180
+ }
1181
+ if (deps.adapter.readiness !== 'READY') {
1182
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'adapter_not_ready_before_mutation', { retryable: true, latchEvent: false });
1183
+ wave9Lease = undefined;
1184
+ return { error: `Wave 9 order refused: adapter changed to ${deps.adapter.readiness} before mutation.` };
1185
+ }
1186
+ if (initialWave9Mode === 'LIVE' && !bracketsEnabled(loadBracketMode())) {
1187
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'native_brackets_disabled_before_mutation', { retryable: true, latchEvent: false });
1188
+ wave9Lease = undefined;
1189
+ return { error: 'Wave 9 live order refused: native bracket protection was disabled before mutation.' };
1190
+ }
1191
+ }
1192
+ const submit = async () => {
1193
+ if (!wave9Claimed && !deps.adapter.isLive) {
1194
+ deps.refreshPaperState?.();
1195
+ const positions = await deps.adapter.getPositionsOrNull();
1196
+ if (positions === null) {
1197
+ throw new Error('generic create_order final isolation check could not read paper positions');
1198
+ }
1199
+ if (positions.some(isWave9ManagedPosition)) {
1200
+ throw new Error('Generic create_order refused at mutation boundary: the paper account now contains an atomic Wave 9 position.');
1201
+ }
1202
+ }
1203
+ if (!wave9Claimed && deps.adapter.isLive && deps.checkWave9LiveSymbolOwnership) {
1204
+ let ownership;
1205
+ try {
1206
+ ownership = await deps.checkWave9LiveSymbolOwnership(args.symbol);
1207
+ }
1208
+ catch (err) {
1209
+ throw new Error(`Generic create_order refused at mutation boundary: Wave 9 LIVE ownership ` +
1210
+ `check failed for ${args.symbol} (${formatError(err)}).`);
1211
+ }
1212
+ if (ownership.status === 'owned' || ownership.status === 'ambiguous') {
1213
+ throw new Error(`Generic create_order refused at mutation boundary: ${args.symbol} has ` +
1214
+ `${ownership.status} Wave 9 LIVE ownership (${ownership.reason}).`);
1215
+ }
1216
+ }
1217
+ return deps.adapter.createOrder(args.symbol, side, type, args.amount, args.price, metadata, stashCid ? { clientOrderId: stashCid } : undefined);
1218
+ };
1219
+ const submitWave9 = async () => {
1220
+ if (!wave9Lease)
1221
+ throw new Error('Wave 9 admission lease is unavailable at mutation boundary.');
1222
+ if (currentWave9ExecutionMode(deps) !== initialWave9Mode) {
1223
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'trading_mode_changed_during_activation_check', { retryable: true, latchEvent: false });
1224
+ wave9Lease = undefined;
1225
+ throw new Error('Wave 9 order refused: adapter or trading mode changed during activation verification.');
1226
+ }
1227
+ if (deps.wave9VenueCheck?.() !== 'binance') {
1228
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'execution_venue_changed_at_mutation_boundary', { retryable: true, latchEvent: false });
1229
+ wave9Lease = undefined;
1230
+ throw new Error('Wave 9 order refused: execution venue changed away from Binance at mutation boundary.');
1231
+ }
1232
+ let currentFingerprint;
1233
+ if (initialWave9Mode === 'LIVE') {
1234
+ const ownership = deps.checkWave9LiveSymbolOwnership
1235
+ ? await deps.checkWave9LiveSymbolOwnership(args.symbol)
1236
+ : await inspectWave9LiveSymbolOwnership(deps.adapter, deps.wave9LiveLedger, args.symbol);
1237
+ if (ownership.status !== 'unowned'
1238
+ || ownership.reason === 'authoritative_symbol_flat') {
1239
+ deps.wave9AdmissionGuard.abort(wave9Lease, `live_symbol_ownership_changed_before_mutation:${ownership.status}:${ownership.reason}`, { retryable: true, latchEvent: false });
1240
+ wave9Lease = undefined;
1241
+ throw new Error(`Wave 9 live order refused: candidate-symbol ownership changed before mutation ` +
1242
+ `(${ownership.status}:${ownership.reason}); refresh get_wave9_status.`);
1243
+ }
1244
+ currentFingerprint = (await deps.captureWave9AccountFingerprint()).fingerprint;
1245
+ }
1246
+ else {
1247
+ deps.refreshPaperState?.();
1248
+ currentFingerprint = captureWave9PaperAccountSnapshot(deps.adapter).fingerprint;
1249
+ }
1250
+ deps.wave9AdmissionGuard.revalidate(wave9Lease, currentFingerprint);
1251
+ if (initialWave9Mode === 'LIVE') {
1252
+ const preEntryFlat = await confirmWave9LivePositionFlat(deps.adapter, args.symbol);
1253
+ if (!preEntryFlat.confirmed) {
1254
+ deps.wave9AdmissionGuard.abort(wave9Lease, `live_candidate_symbol_not_stably_flat_before_entry:${preEntryFlat.detail}`, { retryable: true, latchEvent: false });
1255
+ wave9Lease = undefined;
1256
+ throw new Error(`Wave 9 live order refused: candidate symbol was not authoritatively stably flat ` +
1257
+ `immediately before entry (${preEntryFlat.detail}). No order was submitted; refresh ` +
1258
+ 'get_wave9_status.');
1259
+ }
1260
+ const refuseAfterFlatProof = (reason, detail) => {
1261
+ if (wave9Lease) {
1262
+ deps.wave9AdmissionGuard.abort(wave9Lease, reason, { retryable: true, latchEvent: false });
1263
+ }
1264
+ wave9Lease = undefined;
1265
+ throw new Error(`${detail} No order was submitted; refresh get_wave9_status.`);
1266
+ };
1267
+ let postFlatActivation;
1268
+ try {
1269
+ postFlatActivation = deps.wave9ActivationCheck
1270
+ ? await deps.wave9ActivationCheck()
1271
+ : { available: false, active: false };
1272
+ }
1273
+ catch {
1274
+ postFlatActivation = { available: false, active: false };
1275
+ }
1276
+ if (!postFlatActivation.available || !postFlatActivation.active) {
1277
+ refuseAfterFlatProof('activation_unavailable_or_inactive_after_flat_proof', 'Wave 9 live order refused: the strategy was toggled off or activation could not be re-verified after the stable-flat proof.');
1278
+ }
1279
+ if (currentWave9ExecutionMode(deps) !== initialWave9Mode) {
1280
+ refuseAfterFlatProof('trading_mode_changed_after_flat_proof', 'Wave 9 live order refused: adapter or trading mode changed after the stable-flat proof.');
1281
+ }
1282
+ if (deps.wave9VenueCheck?.() !== 'binance') {
1283
+ refuseAfterFlatProof('execution_venue_changed_after_flat_proof', 'Wave 9 live order refused: execution venue changed away from Binance after the stable-flat proof.');
1284
+ }
1285
+ if (deps.adapter.readiness !== 'READY') {
1286
+ refuseAfterFlatProof('adapter_not_ready_after_flat_proof', `Wave 9 live order refused: adapter changed to ${deps.adapter.readiness} after the stable-flat proof.`);
1287
+ }
1288
+ if (!bracketsEnabled(loadBracketMode())) {
1289
+ refuseAfterFlatProof('native_brackets_disabled_after_flat_proof', 'Wave 9 live order refused: native bracket protection was disabled after the stable-flat proof.');
1290
+ }
1291
+ let postFlatFingerprint;
1292
+ try {
1293
+ postFlatFingerprint = (await deps.captureWave9AccountFingerprint()).fingerprint;
1294
+ }
1295
+ catch (error) {
1296
+ refuseAfterFlatProof(`live_account_fingerprint_unavailable_after_flat_proof:${formatError(error)}`, `Wave 9 live order refused: the LIVE account fingerprint could not be refreshed ` +
1297
+ `after the stable-flat proof (${formatError(error)}).`);
1298
+ }
1299
+ try {
1300
+ deps.wave9AdmissionGuard.revalidate(wave9Lease, postFlatFingerprint);
1301
+ }
1302
+ catch (error) {
1303
+ refuseAfterFlatProof(`authorization_revalidation_failed_after_flat_proof:${formatError(error)}`, `Wave 9 live order refused: authorization or LIVE account identity changed during ` +
1304
+ `the stable-flat proof (${formatError(error)}).`);
1305
+ }
1306
+ const [eventTime, , strategy, direction] = args.candidate_id.split('|');
1307
+ await deps.wave9LiveLedger.recordAttempt({
1308
+ candidateId: args.candidate_id,
1309
+ missionId: args.mission_id,
1310
+ eventTime,
1311
+ symbol: args.symbol,
1312
+ strategy: strategy,
1313
+ direction: direction,
1314
+ side: side,
1315
+ quantity: args.amount,
1316
+ initialStop: args.stopPrice,
1317
+ clientOrderId: stashCid,
1318
+ });
1319
+ }
1320
+ const finishLiveEntryRecovery = (recovery, source) => {
1321
+ if (recovery.status === 'protected') {
1322
+ deps.wave9AdmissionGuard.commit(wave9Lease);
1323
+ wave9Lease = undefined;
1324
+ if (recovery.order)
1325
+ return recovery.order;
1326
+ throw new Error(`Wave 9 LIVE ${source}, but the entry was recovered OPEN and protected. ` +
1327
+ `Do not retry this candidate. ${recovery.detail}`);
1328
+ }
1329
+ deps.wave9AdmissionGuard.abort(wave9Lease, recovery.status === 'terminal'
1330
+ ? 'live_entry_recovery_confirmed_terminal'
1331
+ : 'live_entry_recovery_manual_intervention');
1332
+ wave9Lease = undefined;
1333
+ throw new Error(recovery.status === 'terminal'
1334
+ ? `Wave 9 LIVE ${source} resolved safely and is terminal. ${recovery.detail}`
1335
+ : `CRITICAL: ${recovery.detail}`);
1336
+ };
1337
+ let order;
1338
+ try {
1339
+ order = await submit();
1340
+ }
1341
+ catch (submitError) {
1342
+ if (initialWave9Mode !== 'LIVE')
1343
+ throw submitError;
1344
+ const recovery = await recoverWave9LiveSubmitThrow({
1345
+ adapter: deps.adapter,
1346
+ ledger: deps.wave9LiveLedger,
1347
+ candidateId: args.candidate_id,
1348
+ clientOrderId: stashCid,
1349
+ symbol: args.symbol,
1350
+ side: side,
1351
+ stopPrice: args.stopPrice,
1352
+ authorizedQuantity: args.amount,
1353
+ submitError,
1354
+ confirmStop: deps.confirmWave9LiveStop,
1355
+ residualProtector: deps.reprotectWave9LiveResidual,
1356
+ });
1357
+ return finishLiveEntryRecovery(recovery, 'submit acknowledgement failed');
1358
+ }
1359
+ const filledQuantity = typeof order.filled === 'number'
1360
+ && Number.isFinite(order.filled)
1361
+ && order.filled > 0
1362
+ ? order.filled
1363
+ : undefined;
1364
+ if (filledQuantity === undefined) {
1365
+ if (initialWave9Mode === 'LIVE') {
1366
+ const recovery = await recoverWave9LiveSubmitThrow({
1367
+ adapter: deps.adapter,
1368
+ ledger: deps.wave9LiveLedger,
1369
+ candidateId: args.candidate_id,
1370
+ clientOrderId: stashCid,
1371
+ symbol: args.symbol,
1372
+ side: side,
1373
+ stopPrice: args.stopPrice,
1374
+ authorizedQuantity: args.amount,
1375
+ submitError: new Error('market acknowledgement lacked a positive confirmed fill quantity'),
1376
+ acknowledgedOrder: order,
1377
+ confirmStop: deps.confirmWave9LiveStop,
1378
+ residualProtector: deps.reprotectWave9LiveResidual,
1379
+ });
1380
+ return finishLiveEntryRecovery(recovery, 'market acknowledgement lacked a positive confirmed fill quantity');
1381
+ }
1382
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'market_order_not_filled');
1383
+ wave9Lease = undefined;
1384
+ throw new Error('Wave 9 market order lacks a positive confirmed fill quantity; admission batch latched.');
1385
+ }
1386
+ if (initialWave9Mode === 'LIVE'
1387
+ && !candidateBoundFoundOrder({ status: 'found', order }, stashCid, args.symbol, side)) {
1388
+ const recovery = await recoverWave9LiveSubmitThrow({
1389
+ adapter: deps.adapter,
1390
+ ledger: deps.wave9LiveLedger,
1391
+ candidateId: args.candidate_id,
1392
+ clientOrderId: stashCid,
1393
+ symbol: args.symbol,
1394
+ side: side,
1395
+ stopPrice: args.stopPrice,
1396
+ authorizedQuantity: args.amount,
1397
+ submitError: new Error('market acknowledgement had malformed or mismatched candidate identity'),
1398
+ confirmStop: deps.confirmWave9LiveStop,
1399
+ residualProtector: deps.reprotectWave9LiveResidual,
1400
+ });
1401
+ return finishLiveEntryRecovery(recovery, 'market acknowledgement had malformed or mismatched candidate identity');
1402
+ }
1403
+ if (initialWave9Mode === 'LIVE' && order.status === 'open') {
1404
+ const recovery = await recoverWave9LiveSubmitThrow({
1405
+ adapter: deps.adapter,
1406
+ ledger: deps.wave9LiveLedger,
1407
+ candidateId: args.candidate_id,
1408
+ clientOrderId: stashCid,
1409
+ symbol: args.symbol,
1410
+ side: side,
1411
+ stopPrice: args.stopPrice,
1412
+ authorizedQuantity: args.amount,
1413
+ submitError: new Error('market acknowledgement left an open entry remainder'),
1414
+ acknowledgedOrder: order,
1415
+ confirmStop: deps.confirmWave9LiveStop,
1416
+ residualProtector: deps.reprotectWave9LiveResidual,
1417
+ });
1418
+ return finishLiveEntryRecovery(recovery, 'market acknowledgement left an open entry remainder');
1419
+ }
1420
+ if (initialWave9Mode === 'LIVE') {
1421
+ const entryPrice = confirmedWave9EntryPrice(order, side, args.stopPrice);
1422
+ const exchangeOrderId = confirmedWave9ExchangeOrderId(order);
1423
+ let openPersistenceError;
1424
+ try {
1425
+ await deps.wave9LiveLedger.markOpen(args.candidate_id, {
1426
+ filledQuantity,
1427
+ ...(exchangeOrderId ? { exchangeOrderId } : {}),
1428
+ ...(entryPrice !== undefined ? { entryPrice } : {}),
1429
+ });
1430
+ }
1431
+ catch (err) {
1432
+ openPersistenceError = formatError(err);
1433
+ }
1434
+ if (openPersistenceError) {
1435
+ const flatten = await flattenFailedWave9LiveEntry(deps.adapter, deps.wave9LiveLedger, args.symbol, args.candidate_id, {
1436
+ side: side,
1437
+ maximumQuantity: Math.min(filledQuantity, args.amount),
1438
+ }, deps.reprotectWave9LiveResidual);
1439
+ deps.wave9AdmissionGuard.abort(wave9Lease, flatten.confirmedFlat
1440
+ ? 'live_open_persistence_failed_confirmed_flat'
1441
+ : 'live_open_persistence_failed_residual_exposure');
1442
+ wave9Lease = undefined;
1443
+ const flattenError = flatten.flattenError
1444
+ ? ` Close submission error: ${flatten.flattenError}.`
1445
+ : '';
1446
+ if (flatten.confirmedFlat && !flatten.terminalPersistenceError) {
1447
+ throw new Error(`Wave 9 confirmed fill could not be durably marked OPEN (${openPersistenceError}); ` +
1448
+ `${flatten.closeAttempted
1449
+ ? 'the position was auto-flattened and confirmed flat'
1450
+ : 'the exchange position was already confirmed stably flat'}, and its replay tombstone was ` +
1451
+ `persisted terminal.${flattenError}`);
1452
+ }
1453
+ if (flatten.confirmedFlat) {
1454
+ throw new Error(`Wave 9 confirmed fill could not be durably marked OPEN (${openPersistenceError}). ` +
1455
+ `The exchange is confirmed flat, but durable CLOSED persistence also failed ` +
1456
+ `(${flatten.terminalPersistenceError}).${flattenError}`);
1457
+ }
1458
+ throw new Error(`CRITICAL: Wave 9 confirmed fill could not be durably marked OPEN ` +
1459
+ `(${openPersistenceError}), and ${flatten.closeAttempted
1460
+ ? 'emergency flatten is not confirmed flat'
1461
+ : 'the exposure identity fence refused symbol-wide flatten'} ` +
1462
+ `(${flatten.confirmationDetail}). In-memory OPEN ownership was retained where possible; ` +
1463
+ `the entry capability is consumed and latched.${flattenError}${residualProtectionMessage(flatten)}`);
1464
+ }
1465
+ let stopConfirmed = false;
1466
+ try {
1467
+ stopConfirmed = await deps.confirmWave9LiveStop({
1468
+ candidateId: args.candidate_id,
1469
+ symbol: args.symbol,
1470
+ entryClientOrderId: stashCid,
1471
+ entrySide: side,
1472
+ stopPrice: args.stopPrice,
1473
+ filledQuantity,
1474
+ });
1475
+ }
1476
+ catch {
1477
+ stopConfirmed = false;
1478
+ }
1479
+ if (!stopConfirmed) {
1480
+ const flatten = await flattenFailedWave9LiveEntry(deps.adapter, deps.wave9LiveLedger, args.symbol, args.candidate_id, {
1481
+ side: side,
1482
+ maximumQuantity: Math.min(filledQuantity, args.amount),
1483
+ }, deps.reprotectWave9LiveResidual);
1484
+ deps.wave9AdmissionGuard.abort(wave9Lease, flatten.confirmedFlat
1485
+ ? 'live_stop_unconfirmed_confirmed_flat'
1486
+ : 'live_stop_unconfirmed_residual_exposure');
1487
+ wave9Lease = undefined;
1488
+ const flattenError = flatten.flattenError
1489
+ ? ` Close submission error: ${flatten.flattenError}.`
1490
+ : '';
1491
+ if (flatten.confirmedFlat && !flatten.terminalPersistenceError) {
1492
+ throw new Error(`Wave 9 live stop was not confirmed; ${flatten.closeAttempted
1493
+ ? 'the filled position was auto-flattened and confirmed flat'
1494
+ : 'the exchange position was already confirmed stably flat'}, and its durable ` +
1495
+ `execution row is terminal.${flattenError}`);
1496
+ }
1497
+ if (flatten.confirmedFlat) {
1498
+ throw new Error(`Wave 9 live stop was not confirmed. The exchange is confirmed flat, but durable ` +
1499
+ `CLOSED persistence failed (${flatten.terminalPersistenceError}).${flattenError}`);
1500
+ }
1501
+ throw new Error(`CRITICAL: Wave 9 live stop was not confirmed and ${flatten.closeAttempted
1502
+ ? 'emergency flatten is not confirmed flat'
1503
+ : 'the exposure identity fence refused symbol-wide flatten'} ` +
1504
+ `(${flatten.confirmationDetail}). Durable ownership remains OPEN; the entry ` +
1505
+ `capability is consumed and latched.${flattenError}${residualProtectionMessage(flatten)}`);
1506
+ }
1507
+ }
1508
+ deps.wave9AdmissionGuard.commit(wave9Lease);
1509
+ wave9Lease = undefined;
1510
+ return order;
1511
+ };
1512
+ const order = wave9Claimed
1513
+ ? await deps.operationLock.withAccountLock(`wave9-create-final:${args.symbol}`, submitWave9)
1514
+ : deps.operationLock
1515
+ ? await deps.operationLock.withAccountLock(`generic-create-final:${args.symbol}`, submit)
1516
+ : await submit();
1517
+ // Auto-capture entry to the Position Decision Journal — fail-open, never
1518
+ // block the trading hot path on a webapp ingest blip.
1519
+ if (deps.autoCapture) {
1520
+ if (stashCid &&
1521
+ deps.autoCapture.pendingEntries &&
1522
+ typeof order.id === 'string' &&
1523
+ order.id.length > 0) {
1524
+ deps.autoCapture.pendingEntries.promote(stashCid, order.id);
1525
+ }
1526
+ const filledNow = typeof order.filled === 'number' && order.filled > 0;
1527
+ if (filledNow) {
1528
+ // Order filled at submit (market, or limit-that-crossed). Capture
1529
+ // synchronously — same as the original PR1 path.
1530
+ onCreateOrderFilled(deps.autoCapture, { symbol: args.symbol, side: side, metadata }, order).catch((err) => {
1531
+ // eslint-disable-next-line no-console
1532
+ console.warn(`[position-auto-capture] entry capture threw: ${formatError(err)}`);
1533
+ });
1534
+ }
1535
+ }
1536
+ return order;
1537
+ }
1538
+ catch (err) {
1539
+ if (wave9Lease) {
1540
+ deps.wave9AdmissionGuard.abort(wave9Lease, `order_execution_failed:${formatError(err)}`);
1541
+ wave9Lease = undefined;
1542
+ }
1543
+ return { error: formatError(err) };
1544
+ }
1545
+ }
1546
+ catch (err) {
1547
+ if (!wave9Claimed)
1548
+ throw err;
1549
+ if (wave9Lease) {
1550
+ deps.wave9AdmissionGuard.abort(wave9Lease, `pre_mutation_processing_failed:${formatError(err)}`, { retryable: true, latchEvent: false });
1551
+ wave9Lease = undefined;
1552
+ }
1553
+ return { error: formatError(err) };
1554
+ }
1555
+ }