@jinn-network/client 0.1.6 → 0.1.7-canary.17a8ecb8

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 (351) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +67 -1
  3. package/deployments/deployment-jinn-mvi-l1-sepolia-fast.json +23 -4
  4. package/deployments/deployment-jinn-mvi-l1-sepolia.json +23 -4
  5. package/deployments/deployment-jinn-mvi-l2-baseSepolia.json +5 -4
  6. package/dist/adapters/mech/adapter.d.ts +38 -1
  7. package/dist/adapters/mech/adapter.js +268 -57
  8. package/dist/adapters/mech/adapter.js.map +1 -1
  9. package/dist/adapters/mech/contracts.d.ts +17 -4
  10. package/dist/adapters/mech/contracts.js +8 -2
  11. package/dist/adapters/mech/contracts.js.map +1 -1
  12. package/dist/adapters/mech/safe-revert.d.ts +20 -0
  13. package/dist/adapters/mech/safe-revert.js +12 -4
  14. package/dist/adapters/mech/safe-revert.js.map +1 -1
  15. package/dist/adapters/mech/safe.d.ts +6 -2
  16. package/dist/adapters/mech/safe.js +32 -11
  17. package/dist/adapters/mech/safe.js.map +1 -1
  18. package/dist/adapters/mech/types.d.ts +6 -1
  19. package/dist/adapters/mech/types.js.map +1 -1
  20. package/dist/adapters/mech/verdict-code.d.ts +1 -0
  21. package/dist/adapters/mech/verdict-code.js +18 -0
  22. package/dist/adapters/mech/verdict-code.js.map +1 -1
  23. package/dist/api/activity-events-endpoint.d.ts +14 -0
  24. package/dist/api/activity-events-endpoint.js +59 -0
  25. package/dist/api/activity-events-endpoint.js.map +1 -0
  26. package/dist/api/admin-endpoint.d.ts +15 -3
  27. package/dist/api/admin-endpoint.js +24 -2
  28. package/dist/api/admin-endpoint.js.map +1 -1
  29. package/dist/api/bootstrap-endpoint.d.ts +1 -2
  30. package/dist/api/bootstrap-endpoint.js +49 -1
  31. package/dist/api/bootstrap-endpoint.js.map +1 -1
  32. package/dist/api/codex-doctor-endpoint.d.ts +73 -0
  33. package/dist/api/codex-doctor-endpoint.js +177 -0
  34. package/dist/api/codex-doctor-endpoint.js.map +1 -0
  35. package/dist/api/discovery-endpoint.d.ts +1 -0
  36. package/dist/api/discovery-endpoint.js +26 -0
  37. package/dist/api/discovery-endpoint.js.map +1 -1
  38. package/dist/api/fleet-build.d.ts +1 -0
  39. package/dist/api/fleet-build.js +2 -1
  40. package/dist/api/fleet-build.js.map +1 -1
  41. package/dist/api/gather-status.d.ts +14 -0
  42. package/dist/api/gather-status.js +494 -19
  43. package/dist/api/gather-status.js.map +1 -1
  44. package/dist/api/hermes-doctor-endpoint.d.ts +117 -0
  45. package/dist/api/hermes-doctor-endpoint.js +229 -23
  46. package/dist/api/hermes-doctor-endpoint.js.map +1 -1
  47. package/dist/api/launcher-status.d.ts +22 -17
  48. package/dist/api/launcher-status.js +13 -11
  49. package/dist/api/launcher-status.js.map +1 -1
  50. package/dist/api/launcher-tasks.d.ts +1 -1
  51. package/dist/api/launcher-tasks.js +12 -8
  52. package/dist/api/launcher-tasks.js.map +1 -1
  53. package/dist/api/portfolio-v0-build.d.ts +10 -0
  54. package/dist/api/portfolio-v0-build.js +24 -5
  55. package/dist/api/portfolio-v0-build.js.map +1 -1
  56. package/dist/api/prediction-v1-build.d.ts +10 -0
  57. package/dist/api/prediction-v1-build.js +7 -1
  58. package/dist/api/prediction-v1-build.js.map +1 -1
  59. package/dist/api/server.d.ts +31 -1
  60. package/dist/api/server.js +72 -1
  61. package/dist/api/server.js.map +1 -1
  62. package/dist/api/setup-endpoints.d.ts +16 -0
  63. package/dist/api/setup-endpoints.js +89 -135
  64. package/dist/api/setup-endpoints.js.map +1 -1
  65. package/dist/api/setup-retry-endpoint.d.ts +19 -0
  66. package/dist/api/setup-retry-endpoint.js +32 -0
  67. package/dist/api/setup-retry-endpoint.js.map +1 -0
  68. package/dist/api/solvernets-endpoints.d.ts +8 -0
  69. package/dist/api/solvernets-endpoints.js +71 -43
  70. package/dist/api/solvernets-endpoints.js.map +1 -1
  71. package/dist/api/status-build.d.ts +112 -0
  72. package/dist/api/status-build.js +98 -18
  73. package/dist/api/status-build.js.map +1 -1
  74. package/dist/api/task-run-routing.d.ts +7 -0
  75. package/dist/api/task-run-routing.js +12 -0
  76. package/dist/api/task-run-routing.js.map +1 -0
  77. package/dist/api/task-runs-build.d.ts +21 -0
  78. package/dist/api/task-runs-build.js +14 -1
  79. package/dist/api/task-runs-build.js.map +1 -1
  80. package/dist/build-info.json +4 -4
  81. package/dist/build-meta.json +1 -1
  82. package/dist/chain-read-errors.d.ts +10 -0
  83. package/dist/chain-read-errors.js +15 -0
  84. package/dist/chain-read-errors.js.map +1 -1
  85. package/dist/cli/commands/auth.js +1 -1
  86. package/dist/cli/commands/auth.js.map +1 -1
  87. package/dist/cli/commands/create.js +3 -2
  88. package/dist/cli/commands/create.js.map +1 -1
  89. package/dist/cli/commands/doctor.d.ts +2 -0
  90. package/dist/cli/commands/doctor.js +2 -0
  91. package/dist/cli/commands/doctor.js.map +1 -1
  92. package/dist/cli/commands/rewards.js +11 -7
  93. package/dist/cli/commands/rewards.js.map +1 -1
  94. package/dist/cli/commands/solver-nets.js +101 -15
  95. package/dist/cli/commands/solver-nets.js.map +1 -1
  96. package/dist/cli/commands/solver-plugins-block.d.ts +33 -0
  97. package/dist/cli/commands/solver-plugins-block.js +118 -0
  98. package/dist/cli/commands/solver-plugins-block.js.map +1 -0
  99. package/dist/cli/commands/solver-plugins-feedback.d.ts +72 -0
  100. package/dist/cli/commands/solver-plugins-feedback.js +262 -0
  101. package/dist/cli/commands/solver-plugins-feedback.js.map +1 -0
  102. package/dist/cli/commands/solver-plugins-read.d.ts +54 -0
  103. package/dist/cli/commands/solver-plugins-read.js +259 -0
  104. package/dist/cli/commands/solver-plugins-read.js.map +1 -0
  105. package/dist/cli/commands/solver-plugins.d.ts +35 -0
  106. package/dist/cli/commands/solver-plugins.js +399 -2
  107. package/dist/cli/commands/solver-plugins.js.map +1 -1
  108. package/dist/cli/commands/status.js +1 -1
  109. package/dist/cli/commands/status.js.map +1 -1
  110. package/dist/cli/commands/tasks.js +101 -11
  111. package/dist/cli/commands/tasks.js.map +1 -1
  112. package/dist/cli/commands/update.d.ts +10 -0
  113. package/dist/cli/commands/update.js +36 -0
  114. package/dist/cli/commands/update.js.map +1 -1
  115. package/dist/cli/introspection-context.js +5 -0
  116. package/dist/cli/introspection-context.js.map +1 -1
  117. package/dist/cli/task-native-readiness.d.ts +10 -1
  118. package/dist/cli/task-native-readiness.js +30 -6
  119. package/dist/cli/task-native-readiness.js.map +1 -1
  120. package/dist/config.d.ts +273 -235
  121. package/dist/config.js +305 -114
  122. package/dist/config.js.map +1 -1
  123. package/dist/daemon/checkpoint-loop.d.ts +48 -0
  124. package/dist/daemon/checkpoint-loop.js +76 -0
  125. package/dist/daemon/checkpoint-loop.js.map +1 -0
  126. package/dist/daemon/creator.d.ts +1 -1
  127. package/dist/daemon/creator.js +7 -3
  128. package/dist/daemon/creator.js.map +1 -1
  129. package/dist/daemon/daemon.d.ts +22 -0
  130. package/dist/daemon/daemon.js +156 -23
  131. package/dist/daemon/daemon.js.map +1 -1
  132. package/dist/daemon/eviction-loop.d.ts +40 -0
  133. package/dist/daemon/eviction-loop.js +67 -0
  134. package/dist/daemon/eviction-loop.js.map +1 -0
  135. package/dist/daemon/gate-logger.d.ts +9 -0
  136. package/dist/daemon/gate-logger.js +2 -0
  137. package/dist/daemon/gate-logger.js.map +1 -0
  138. package/dist/daemon/jinn-claim-loop-wiring.d.ts +33 -0
  139. package/dist/daemon/jinn-claim-loop-wiring.js +40 -0
  140. package/dist/daemon/jinn-claim-loop-wiring.js.map +1 -0
  141. package/dist/daemon/jinn-claim-loop.d.ts +24 -17
  142. package/dist/daemon/jinn-claim-loop.js +77 -23
  143. package/dist/daemon/jinn-claim-loop.js.map +1 -1
  144. package/dist/daemon/readiness-gate.d.ts +1 -4
  145. package/dist/daemon/readiness-gate.js.map +1 -1
  146. package/dist/daemon/skip-log-dedup.d.ts +69 -0
  147. package/dist/daemon/skip-log-dedup.js +106 -0
  148. package/dist/daemon/skip-log-dedup.js.map +1 -0
  149. package/dist/daemon/spend-cap-gate.d.ts +40 -0
  150. package/dist/daemon/spend-cap-gate.js +46 -0
  151. package/dist/daemon/spend-cap-gate.js.map +1 -0
  152. package/dist/dashboard/assets/index-8yHQgi7p.js +345 -0
  153. package/dist/dashboard/assets/index-BOBhJ76-.css +32 -0
  154. package/dist/dashboard/index.html +2 -2
  155. package/dist/discovery/factory.d.ts +17 -5
  156. package/dist/discovery/factory.js +46 -18
  157. package/dist/discovery/factory.js.map +1 -1
  158. package/dist/discovery/http.js +142 -3
  159. package/dist/discovery/http.js.map +1 -1
  160. package/dist/discovery/onchain.d.ts +5 -0
  161. package/dist/discovery/onchain.js +407 -15
  162. package/dist/discovery/onchain.js.map +1 -1
  163. package/dist/discovery/types.d.ts +45 -1
  164. package/dist/discovery/types.js +8 -10
  165. package/dist/discovery/types.js.map +1 -1
  166. package/dist/discovery/with-fallback.d.ts +7 -0
  167. package/dist/discovery/with-fallback.js +10 -0
  168. package/dist/discovery/with-fallback.js.map +1 -1
  169. package/dist/earning/bootstrap.d.ts +92 -1
  170. package/dist/earning/bootstrap.js +203 -63
  171. package/dist/earning/bootstrap.js.map +1 -1
  172. package/dist/earning/contracts.d.ts +14 -0
  173. package/dist/earning/contracts.js +17 -5
  174. package/dist/earning/contracts.js.map +1 -1
  175. package/dist/earning/funding-plan.js +27 -18
  176. package/dist/earning/funding-plan.js.map +1 -1
  177. package/dist/earning/jinn-rewards.d.ts +46 -0
  178. package/dist/earning/jinn-rewards.js +32 -0
  179. package/dist/earning/jinn-rewards.js.map +1 -1
  180. package/dist/earning/safe-adapter.d.ts +2 -0
  181. package/dist/earning/safe-adapter.js +37 -11
  182. package/dist/earning/safe-adapter.js.map +1 -1
  183. package/dist/earning/store.d.ts +8 -0
  184. package/dist/earning/store.js.map +1 -1
  185. package/dist/earning/testnet-setup-migration.d.ts +12 -0
  186. package/dist/earning/testnet-setup-migration.js +27 -1
  187. package/dist/earning/testnet-setup-migration.js.map +1 -1
  188. package/dist/earning/types.d.ts +21 -6
  189. package/dist/earning/viem-clients.d.ts +11 -4
  190. package/dist/earning/viem-clients.js +14 -5
  191. package/dist/earning/viem-clients.js.map +1 -1
  192. package/dist/erc8004/reputation.d.ts +8 -0
  193. package/dist/erc8004/reputation.js +22 -3
  194. package/dist/erc8004/reputation.js.map +1 -1
  195. package/dist/events/types.d.ts +2 -2
  196. package/dist/harnesses/cost-estimates.d.ts +145 -0
  197. package/dist/harnesses/cost-estimates.js +297 -0
  198. package/dist/harnesses/cost-estimates.js.map +1 -0
  199. package/dist/harnesses/engine/engine.d.ts +72 -0
  200. package/dist/harnesses/engine/engine.js +118 -8
  201. package/dist/harnesses/engine/engine.js.map +1 -1
  202. package/dist/harnesses/engine/persistence.d.ts +51 -1
  203. package/dist/harnesses/engine/persistence.js +118 -5
  204. package/dist/harnesses/engine/persistence.js.map +1 -1
  205. package/dist/harnesses/engine/work-dir-reaper.d.ts +65 -0
  206. package/dist/harnesses/engine/work-dir-reaper.js +100 -0
  207. package/dist/harnesses/engine/work-dir-reaper.js.map +1 -0
  208. package/dist/harnesses/impls/hermes-agent/adapter.js +40 -0
  209. package/dist/harnesses/impls/hermes-agent/adapter.js.map +1 -1
  210. package/dist/harnesses/impls/hermes-agent/bootstrap.d.ts +20 -0
  211. package/dist/harnesses/impls/hermes-agent/bootstrap.js +40 -6
  212. package/dist/harnesses/impls/hermes-agent/bootstrap.js.map +1 -1
  213. package/dist/harnesses/impls/hermes-agent/harness.d.ts +59 -1
  214. package/dist/harnesses/impls/hermes-agent/harness.js +104 -0
  215. package/dist/harnesses/impls/hermes-agent/harness.js.map +1 -1
  216. package/dist/harnesses/impls/index.d.ts +7 -0
  217. package/dist/harnesses/impls/index.js +16 -1
  218. package/dist/harnesses/impls/index.js.map +1 -1
  219. package/dist/harnesses/impls/learner/harness.d.ts +38 -4
  220. package/dist/harnesses/impls/learner/harness.js +96 -2
  221. package/dist/harnesses/impls/learner/harness.js.map +1 -1
  222. package/dist/harnesses/impls/learner/plugin-path.d.ts +0 -13
  223. package/dist/harnesses/impls/learner/plugin-path.js +35 -15
  224. package/dist/harnesses/impls/learner/plugin-path.js.map +1 -1
  225. package/dist/harnesses/impls/learner/types.d.ts +11 -0
  226. package/dist/harnesses/impls/stub.d.ts +58 -0
  227. package/dist/harnesses/impls/stub.js +89 -0
  228. package/dist/harnesses/impls/stub.js.map +1 -0
  229. package/dist/harnesses/impls/swe-rebench-v2-evaluator/eval-runner.d.ts +69 -50
  230. package/dist/harnesses/impls/swe-rebench-v2-evaluator/eval-runner.js +178 -93
  231. package/dist/harnesses/impls/swe-rebench-v2-evaluator/eval-runner.js.map +1 -1
  232. package/dist/harnesses/impls/swe-rebench-v2-evaluator/harness.d.ts +12 -1
  233. package/dist/harnesses/impls/swe-rebench-v2-evaluator/harness.js +121 -7
  234. package/dist/harnesses/impls/swe-rebench-v2-evaluator/harness.js.map +1 -1
  235. package/dist/harnesses/impls/swe-rebench-v2-evaluator/hf-fetcher.d.ts +88 -4
  236. package/dist/harnesses/impls/swe-rebench-v2-evaluator/hf-fetcher.js +143 -22
  237. package/dist/harnesses/impls/swe-rebench-v2-evaluator/hf-fetcher.js.map +1 -1
  238. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.d.ts +6 -0
  239. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.js +1 -1
  240. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.js.map +1 -1
  241. package/dist/harnesses/readiness-registry.js +9 -1
  242. package/dist/harnesses/readiness-registry.js.map +1 -1
  243. package/dist/main.js +413 -111
  244. package/dist/main.js.map +1 -1
  245. package/dist/observability/emit-event.d.ts +3 -2
  246. package/dist/observability/emit-event.js +22 -1
  247. package/dist/observability/emit-event.js.map +1 -1
  248. package/dist/operator-errors.d.ts +7 -0
  249. package/dist/operator-errors.js +13 -1
  250. package/dist/operator-errors.js.map +1 -1
  251. package/dist/plugins/learner/.claude-plugin/plugin.json +9 -0
  252. package/dist/plugins/learner/.codex-plugin/plugin.json +39 -0
  253. package/dist/plugins/learner/AGENTS.md +40 -0
  254. package/dist/plugins/learner/CLAUDE.md +33 -0
  255. package/dist/plugins/learner/README.md +59 -0
  256. package/dist/plugins/learner/hooks/hooks.json +16 -0
  257. package/dist/plugins/learner/hooks/session-start +38 -0
  258. package/dist/plugins/learner/skills/learn/SKILL.md +412 -0
  259. package/dist/plugins/learner/skills/learn/analyst-prompt.md +68 -0
  260. package/dist/plugins/learner/skills/learn/consolidator-prompt.md +94 -0
  261. package/dist/plugins/learner/skills/learn/explorer-prompt.md +53 -0
  262. package/dist/plugins/learner/skills/learn/planner-prompt.md +87 -0
  263. package/dist/plugins/learner/skills/learn/promoter-prompt.md +113 -0
  264. package/dist/plugins/learner/skills/learn/step-worker-prompt.md +47 -0
  265. package/dist/plugins/learner/skills/learn/strategist-prompt.md +85 -0
  266. package/dist/preflight/rpc-network.d.ts +40 -0
  267. package/dist/preflight/rpc-network.js +67 -1
  268. package/dist/preflight/rpc-network.js.map +1 -1
  269. package/dist/restart-daemon.d.ts +90 -0
  270. package/dist/restart-daemon.js +95 -0
  271. package/dist/restart-daemon.js.map +1 -0
  272. package/dist/rpc/transport.d.ts +109 -0
  273. package/dist/rpc/transport.js +220 -0
  274. package/dist/rpc/transport.js.map +1 -0
  275. package/dist/scripts/donation-consumption-acceptance.js +7 -28
  276. package/dist/scripts/donation-consumption-acceptance.js.map +1 -1
  277. package/dist/setup/halt-mode.d.ts +14 -0
  278. package/dist/setup/halt-mode.js +17 -0
  279. package/dist/setup/halt-mode.js.map +1 -0
  280. package/dist/solver-nets/prediction-operator-ux.d.ts +1 -2
  281. package/dist/solver-nets/prediction-operator-ux.js +90 -47
  282. package/dist/solver-nets/prediction-operator-ux.js.map +1 -1
  283. package/dist/solver-nets/registry.d.ts +20 -1
  284. package/dist/solver-nets/registry.js +38 -25
  285. package/dist/solver-nets/registry.js.map +1 -1
  286. package/dist/solver-types/_swe-rebench-v2-pool-cache.d.ts +58 -0
  287. package/dist/solver-types/_swe-rebench-v2-pool-cache.js +87 -0
  288. package/dist/solver-types/_swe-rebench-v2-pool-cache.js.map +1 -0
  289. package/dist/solver-types/_swe-rebench-v2-pool.d.ts +9 -2
  290. package/dist/solver-types/_swe-rebench-v2-pool.js +15 -20
  291. package/dist/solver-types/_swe-rebench-v2-pool.js.map +1 -1
  292. package/dist/solver-types/_swe-rebench-v2-substrate.d.ts +1 -0
  293. package/dist/solver-types/_swe-rebench-v2-substrate.js +10 -0
  294. package/dist/solver-types/_swe-rebench-v2-substrate.js.map +1 -1
  295. package/dist/solver-types/_swe-rebench-v2-validated-pool.d.ts +94 -1
  296. package/dist/solver-types/_swe-rebench-v2-validated-pool.js +305 -39
  297. package/dist/solver-types/_swe-rebench-v2-validated-pool.js.map +1 -1
  298. package/dist/solver-types/swe-rebench-v2-auto.d.ts +22 -7
  299. package/dist/solver-types/swe-rebench-v2-auto.js +45 -20
  300. package/dist/solver-types/swe-rebench-v2-auto.js.map +1 -1
  301. package/dist/solver-types/swe-rebench-v2.d.ts +13 -2
  302. package/dist/solver-types/swe-rebench-v2.js +237 -95
  303. package/dist/solver-types/swe-rebench-v2.js.map +1 -1
  304. package/dist/solvernets/daemon-init.d.ts +10 -2
  305. package/dist/solvernets/daemon-init.js +22 -2
  306. package/dist/solvernets/daemon-init.js.map +1 -1
  307. package/dist/solvernets/launched-record-dispatcher.js +35 -7
  308. package/dist/solvernets/launched-record-dispatcher.js.map +1 -1
  309. package/dist/solvernets/store.d.ts +5 -0
  310. package/dist/solvernets/store.js +1 -0
  311. package/dist/solvernets/store.js.map +1 -1
  312. package/dist/spend/credential.d.ts +8 -0
  313. package/dist/spend/credential.js +30 -0
  314. package/dist/spend/credential.js.map +1 -0
  315. package/dist/spend/daemon-config.d.ts +13 -0
  316. package/dist/spend/daemon-config.js +24 -0
  317. package/dist/spend/daemon-config.js.map +1 -0
  318. package/dist/spend/pricing.d.ts +16 -0
  319. package/dist/spend/pricing.js +26 -0
  320. package/dist/spend/pricing.js.map +1 -0
  321. package/dist/spend/record.d.ts +13 -0
  322. package/dist/spend/record.js +30 -0
  323. package/dist/spend/record.js.map +1 -0
  324. package/dist/spend/usage.d.ts +27 -0
  325. package/dist/spend/usage.js +113 -0
  326. package/dist/spend/usage.js.map +1 -0
  327. package/dist/store/store.d.ts +43 -0
  328. package/dist/store/store.js +236 -7
  329. package/dist/store/store.js.map +1 -1
  330. package/dist/tasks/sources.d.ts +18 -1
  331. package/dist/tasks/sources.js +33 -5
  332. package/dist/tasks/sources.js.map +1 -1
  333. package/dist/trajectory/transcript-parsers/types.d.ts +8 -8
  334. package/dist/tx-retry.d.ts +166 -19
  335. package/dist/tx-retry.js +310 -32
  336. package/dist/tx-retry.js.map +1 -1
  337. package/dist/types/payloads/prediction-apy-v0.d.ts +5 -5
  338. package/dist/types/payloads/prediction-v0.d.ts +5 -5
  339. package/dist/types/task-document.d.ts +392 -0
  340. package/dist/types/task-document.js +10 -0
  341. package/dist/types/task-document.js.map +1 -1
  342. package/dist/types/task.d.ts +28 -0
  343. package/dist/util/extract-tx-hash.d.ts +14 -0
  344. package/dist/util/extract-tx-hash.js +19 -0
  345. package/dist/util/extract-tx-hash.js.map +1 -0
  346. package/dist/vendor/@jinn-network/sdk/dist/contracts.js +1 -1
  347. package/dist/vendor/@jinn-network/sdk/dist/solvernets/manifest-schema.d.ts +3 -0
  348. package/dist/vendor/@jinn-network/sdk/dist/solvernets/manifest-schema.js +1 -0
  349. package/package.json +30 -12
  350. package/dist/dashboard/assets/index-DOlzFN8a.css +0 -32
  351. package/dist/dashboard/assets/index-NkZ7CTAT.js +0 -140
package/dist/config.js CHANGED
@@ -18,15 +18,9 @@ import { homedir } from 'node:os';
18
18
  import { dirname, join } from 'node:path';
19
19
  import { z } from 'zod';
20
20
  import { TaskSchema, parseTask } from './types/task.js';
21
- import { canonicalHarnessName, CLAUDE_CODE_HARNESS } from './harnesses/names.js';
22
- /**
23
- * Default `solverNets` is empty per Decision 5 of
24
- * spec/2026-05-05-solvernet-creation-and-launch.md — pre-release, no
25
- * migration burden. Fresh installs no longer seed the `prediction` entry;
26
- * the operator joins SolverNets through the registry (Task 21's
27
- * `joinedSolverNets` block).
28
- */
29
- export const DEFAULT_SOLVER_NETS = {};
21
+ import { canonicalHarnessName } from './harnesses/names.js';
22
+ import { parseRpcUrls } from './rpc/transport.js';
23
+ // ── Schema ──────────────────────────────────────────────────────────────────
30
24
  const HarnessNameSchema = z.string().transform((name) => canonicalHarnessName(name));
31
25
  export const JinnConfigSchema = z.object({
32
26
  /**
@@ -38,19 +32,24 @@ export const JinnConfigSchema = z.object({
38
32
  */
39
33
  network: z.enum(['mainnet', 'testnet']).default('testnet'),
40
34
  /**
41
- * Base RPC endpoint.
42
- * Defaults to https://mainnet.base.org for 'mainnet' and
43
- * https://sepolia.base.org for 'testnet'. Set explicitly to override.
35
+ * Base RPC endpoint(s). Accepts either a single URL string or an array of
36
+ * URLs. When an array (or a comma-separated env var) is supplied, the
37
+ * daemon builds a viem `fallback()` chain in slot order (primary first).
38
+ * See `client/src/rpc/transport.ts` for the wrapper. Defaults to the
39
+ * publicnode+sepolia.base.org two-provider chain on testnet and the public
40
+ * `mainnet.base.org` on mainnet. Set explicitly to override.
41
+ *
42
+ * Env: JINN_RPC_URL / BASE_SEPOLIA_RPC_URL / BASE_RPC_URL (comma-separated).
44
43
  */
45
- rpcUrl: z.string().optional(),
46
- archiveRpcUrl: z.string().optional(),
44
+ rpcUrl: z.union([z.string(), z.array(z.string()).min(1)]).optional(),
45
+ archiveRpcUrl: z.union([z.string(), z.array(z.string()).min(1)]).optional(),
47
46
  /**
48
- * Optional L2 proof/archive RPC endpoint for canonical cross-chain canaries.
47
+ * Optional L2 proof/archive RPC endpoint(s) for canonical cross-chain canaries.
49
48
  * The daemon can use its normal rpcUrl for writes while proof construction
50
49
  * uses this endpoint for historical eth_getProof at OP dispute-game blocks.
51
- * Env: JINN_L2_PROOF_RPC_URL.
50
+ * Accepts string or array form (see rpcUrl). Env: JINN_L2_PROOF_RPC_URL.
52
51
  */
53
- l2ProofRpcUrl: z.string().url().optional(),
52
+ l2ProofRpcUrl: z.union([z.string(), z.array(z.string()).min(1)]).optional(),
54
53
  /** Earning state directory */
55
54
  earningDir: z.string().default(join(homedir(), '.jinn-client', 'earning')),
56
55
  /** SQLite database path */
@@ -70,6 +69,20 @@ export const JinnConfigSchema = z.object({
70
69
  * Set to 0 to disable. Env: JINN_BALANCE_TOPUP_INTERVAL_MS
71
70
  */
72
71
  balanceTopupIntervalMs: z.number().int().min(0).default(300_000),
72
+ /**
73
+ * Interval between eviction-state polls for the staking proxy (ms).
74
+ * Default 60000 (1 min). Set to 0 to disable. Env: JINN_EVICTION_CHECK_INTERVAL_MS
75
+ */
76
+ evictionCheckIntervalMs: z.number().int().min(0).default(60_000),
77
+ /**
78
+ * Interval between proactive `checkpoint()` tx calls to each staked
79
+ * proxy (ms). Keeps `tsCheckpoint` advancing so the activity-rate window
80
+ * stays narrow — without it operators silently fail liveness on realistic
81
+ * cadence (issue #505). Default 300_000 (5 min), matching the standard
82
+ * `livenessPeriod` on stOLAS staking proxies. Set to 0 to disable.
83
+ * Env: JINN_CHECKPOINT_INTERVAL_MS.
84
+ */
85
+ checkpointIntervalMs: z.number().int().min(0).default(300_000),
73
86
  /** HTTP API port */
74
87
  apiPort: z.number().int().positive().default(7331),
75
88
  /**
@@ -97,6 +110,13 @@ export const JinnConfigSchema = z.object({
97
110
  * Default 30 000 ms. Env: JINN_HERMES_DOCTOR_TIMEOUT_MS.
98
111
  */
99
112
  hermesDoctorTimeoutMs: z.number().int().positive().default(30_000),
113
+ /** Path to the `codex` executable. Defaults to `codex` when unset. */
114
+ codexPath: z.string().optional(),
115
+ /**
116
+ * Timeout in ms for `codex --version` health-check runs.
117
+ * Default 30 000 ms. Env: JINN_CODEX_DOCTOR_TIMEOUT_MS.
118
+ */
119
+ codexDoctorTimeoutMs: z.number().int().positive().default(30_000),
100
120
  /**
101
121
  * How the operator runs the daemon. Set once by app-guided setup or the
102
122
  * legacy `jinn auth` compatibility command, then read by every command that
@@ -174,17 +194,19 @@ export const JinnConfigSchema = z.object({
174
194
  jinnMviL2DeploymentPath: z.string().optional(),
175
195
  // ── Cross-chain claim loop (Phase B / jinn-mono-7x5) ─────────────────────
176
196
  /**
177
- * RPC endpoint for the L1 governance chain (Ethereum / Sepolia) where the
178
- * JinnDistributor lives. Required when jinnDistributorAddress is set.
179
- * Env: JINN_ETHEREUM_RPC_URL.
197
+ * RPC endpoint(s) for the L1 governance chain (Ethereum / Sepolia) where
198
+ * the JinnDistributor lives. Accepts string or array form (see rpcUrl) and
199
+ * supports comma-separated env values. Testnet defaults to a public Sepolia
200
+ * RPC; mainnet requires an operator override when L1 submit mode is
201
+ * configured. Env: JINN_ETHEREUM_RPC_URL.
180
202
  */
181
- ethereumRpcUrl: z.string().url().optional(),
203
+ ethereumRpcUrl: z.union([z.string(), z.array(z.string()).min(1)]).optional(),
182
204
  /**
183
- * Optional archive RPC endpoint for the L1 governance chain. Used for
184
- * historical block lookups when constructing canonical-mode proofs.
185
- * Env: JINN_ETHEREUM_ARCHIVE_RPC_URL.
205
+ * Optional archive RPC endpoint(s) for the L1 governance chain. Used for
206
+ * historical block lookups when constructing canonical-mode proofs. Accepts
207
+ * string or array form. Env: JINN_ETHEREUM_ARCHIVE_RPC_URL.
186
208
  */
187
- ethereumArchiveRpcUrl: z.string().url().optional(),
209
+ ethereumArchiveRpcUrl: z.union([z.string(), z.array(z.string()).min(1)]).optional(),
188
210
  /**
189
211
  * L1 network used by the cross-chain claim loop. 'sepolia' tracks Base
190
212
  * Sepolia testnet; 'ethereum' tracks Base mainnet. Defaults to 'sepolia'
@@ -192,10 +214,10 @@ export const JinnConfigSchema = z.object({
192
214
  */
193
215
  jinnL1Network: z.enum(['sepolia', 'ethereum']).default('sepolia'),
194
216
  /**
195
- * JinnDistributor address on the L1 governance chain. Setting this enables
196
- * the cross-chain claim loop. When set, ethereumRpcUrl MUST also be set.
197
- * Resolved from jinnMviL1DeploymentPath when omitted; otherwise a manual
198
- * override. Env: JINN_DISTRIBUTOR_ADDRESS.
217
+ * JinnDistributor address on the L1 governance chain. Required for
218
+ * jinnClaimSubmissionMode='submit'. When set for submit mode, ethereumRpcUrl
219
+ * MUST also be set. Resolved from jinnMviL1DeploymentPath when omitted;
220
+ * otherwise a manual override. Env: JINN_DISTRIBUTOR_ADDRESS.
199
221
  */
200
222
  jinnDistributorAddress: z
201
223
  .string()
@@ -229,6 +251,20 @@ export const JinnConfigSchema = z.object({
229
251
  * Env: JINN_MESSENGER_MODE.
230
252
  */
231
253
  jinnMessengerMode: z.enum(['canonical', 'mock']).default('canonical'),
254
+ /**
255
+ * Claim submission mode. `emit-only` only submits TaskClaimEmitter.emitClaim
256
+ * on L2 and records the resulting ticket. `submit` continues into the L1
257
+ * messenger/distributor path.
258
+ * Env: JINN_CLAIM_SUBMISSION_MODE.
259
+ */
260
+ jinnClaimSubmissionMode: z.enum(['emit-only', 'submit']).default('emit-only'),
261
+ /**
262
+ * Explicit operator gate for the cross-chain JINN claim loop. The schema
263
+ * default stays off for mainnet/unknown networks; loadConfig defaults this
264
+ * on for testnet now that the emitter and standing relayer are live.
265
+ * Env: JINN_CLAIM_LOOP_ENABLED=1|true|yes.
266
+ */
267
+ jinnClaimLoopEnabled: z.boolean().default(false),
232
268
  /**
233
269
  * How often the daemon ticks the cross-chain JINN claim loop (ms). Default
234
270
  * 3 600 000 (1 hour) — well below mainnet challenge windows while
@@ -318,69 +354,7 @@ export const JinnConfigSchema = z.object({
318
354
  })
319
355
  .default({ mode: 'train' }),
320
356
  /**
321
- * SolverNet activation, Harness selection, and operator-configured runtime plugins.
322
- *
323
- * Each entry's `roles` is a non-empty subset of `['solving', 'evaluating']`.
324
- * Multiple roles can run concurrently for the same SolverNet; the
325
- * protocol-level `disallowSolverSelfEvaluation` flag prevents the operator
326
- * from evaluating its own Solutions and the on-chain
327
- * TaskActivityCheckerV3 tracks Solution and Verdict counters independently
328
- * (additive into `eligibleActivityWeight`).
329
- *
330
- * Launcher ownership lives in the launched-record subsystem
331
- * (spec/2026-05-05-solvernet-creation-and-launch.md §11), not in operator
332
- * config — there is no `'launching'` operator role. Legacy entries that
333
- * include `'launching'` in `roles` have it stripped by the preprocessor so
334
- * older config files keep loading.
335
- *
336
- * Backwards-compat: a legacy `role: 'solving' | 'evaluating'` field is
337
- * auto-promoted to `roles: [<role>]` by the zod preprocessor below so
338
- * existing `~/.jinn-client/config.json` files keep loading without an
339
- * operator migration step.
340
- */
341
- solverNets: z.record(z.preprocess((raw) => {
342
- if (typeof raw !== 'object' || raw === null)
343
- return raw;
344
- const obj = raw;
345
- // Promote legacy `role: X` → `roles: [X]` when only the singular form
346
- // is provided. If both are present (mid-migration third-party config),
347
- // `roles` wins and `role` is dropped.
348
- if (Array.isArray(obj['roles']) && obj['roles'].length > 0) {
349
- // Drop legacy `'launching'` entries — operator config no longer
350
- // carries the launcher role; ownership is via launched records.
351
- const filteredRoles = obj['roles'].filter((r) => r !== 'launching');
352
- const { role: _legacyRole, ...rest } = obj;
353
- return { ...rest, roles: filteredRoles };
354
- }
355
- if (typeof obj['role'] === 'string' && (obj['role'] === 'solving' || obj['role'] === 'evaluating')) {
356
- const { role, ...rest } = obj;
357
- return { ...rest, roles: [role] };
358
- }
359
- return obj;
360
- }, z.object({
361
- enabled: z.boolean().default(true),
362
- solverType: z.string(),
363
- roles: z.array(z.enum(['solving', 'evaluating']))
364
- .min(1, 'each SolverNet must enable at least one role')
365
- .default(['solving'])
366
- // Deduplicate to keep downstream consumers simple.
367
- .transform((arr) => Array.from(new Set(arr))),
368
- harness: HarnessNameSchema.default(CLAUDE_CODE_HARNESS),
369
- model: z.string().optional(),
370
- plugins: z.array(z.union([
371
- z.string(),
372
- z.object({
373
- name: z.string().optional(),
374
- source: z.string(),
375
- version: z.string().optional(),
376
- }),
377
- ])).default([]),
378
- taskGenerator: z.object({
379
- enabled: z.boolean().default(true),
380
- }).default({ enabled: true }),
381
- }))).default(DEFAULT_SOLVER_NETS),
382
- /**
383
- * Manifest-keyed joined SolverNets (Task 21).
357
+ * Manifest-keyed joined SolverNets.
384
358
  *
385
359
  * Spec: spec/2026-05-05-solvernet-creation-and-launch.md §12.
386
360
  *
@@ -389,10 +363,10 @@ export const JinnConfigSchema = z.object({
389
363
  * (CIDv0 / CIDv1) — the only stable identifier that maps back to a
390
364
  * launched-instance authority across launchers.
391
365
  *
392
- * Kept structurally separate from legacy `solverNets` for now. The daemon
393
- * narrows this block into runtime SolverNet registry entries on restart, and
394
- * Task 22 collapses both branches into a single manifest-keyed shape once
395
- * the legacy block is fully drained.
366
+ * The daemon narrows this block into runtime SolverNet registry entries on
367
+ * restart. Legacy short-name-keyed `solverNets` blocks on operator config
368
+ * files are auto-migrated into synthetic `legacy:<short-name>`-keyed
369
+ * entries at load time (see `migrateLegacySolverNets`).
396
370
  */
397
371
  joinedSolverNets: z
398
372
  .record(z.string(), z.object({
@@ -536,8 +510,37 @@ export const JinnConfigSchema = z.object({
536
510
  * Env: JINN_REPUTATION_ENABLED
537
511
  */
538
512
  reputationEnabled: z.boolean().default(false),
539
- }).refine((cfg) => !cfg.jinnDistributorAddress || !!cfg.ethereumRpcUrl, {
540
- message: 'ethereumRpcUrl must be set when jinnDistributorAddress is configured ' +
513
+ /**
514
+ * Per-credential daily spend caps (USD). Keys are credential identifiers
515
+ * (e.g. `'anthropic:api-key'`); values are positive numbers representing
516
+ * the maximum USD spend per day for that credential.
517
+ *
518
+ * `JINN_SPEND_CAP_USD` (env-only, consumed directly by the spend-budget
519
+ * subsystem in Task 9) is tracked for provenance in TRACKED_ENV_VARS below
520
+ * but is NOT a config-file field and has no entry in this schema.
521
+ */
522
+ spendCaps: z.record(z.string(), z.number().positive()).optional(),
523
+ /**
524
+ * Operator-local SolverPlugin trust state.
525
+ *
526
+ * `blockedCids` is the list of plug-in CIDs the operator has chosen to
527
+ * refuse to load — populated by `jinn solver-plugins block <cid>` and read
528
+ * at daemon startup. The block list complements the on-chain
529
+ * `giveFeedback(score=0)` write (which is the public-trust signal); the
530
+ * local list is the operator's own refusal to execute, applied even when
531
+ * the network write fails. File-managed only — no env override.
532
+ *
533
+ * See spec/2026-05-26-117-design.md "Failure modes" and "Local-only effects".
534
+ */
535
+ solverPlugins: z
536
+ .object({
537
+ blockedCids: z.array(z.string()).default([]),
538
+ })
539
+ .default({ blockedCids: [] }),
540
+ }).refine((cfg) => cfg.jinnClaimSubmissionMode !== 'submit' ||
541
+ !cfg.jinnDistributorAddress ||
542
+ !!cfg.ethereumRpcUrl, {
543
+ message: 'ethereumRpcUrl must be set when jinnDistributorAddress is configured in submit mode ' +
541
544
  '(env JINN_ETHEREUM_RPC_URL or config field). The cross-chain claim loop ' +
542
545
  'cannot reach the L1 governance chain without it.',
543
546
  path: ['ethereumRpcUrl'],
@@ -557,6 +560,21 @@ export const DEFAULT_CONFIG_PATH = join(DEFAULT_DIR, 'config.json');
557
560
  * historical sync — see ponder.config.ts).
558
561
  */
559
562
  export const DEFAULT_TESTNET_DISCOVERY_URL = 'https://jinn-indexer-production.up.railway.app';
563
+ export const DEFAULT_TESTNET_ETHEREUM_RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com';
564
+ /**
565
+ * Default fallback chain for the L2 measurement chain (Base Sepolia) on
566
+ * testnet. Per AC2 of issue #592:
567
+ * slot 0 — `https://base-sepolia.publicnode.com` (no-auth, 50k-block
568
+ * getLogs cap, no shared-quota cliff).
569
+ * slot 1 — `https://sepolia.base.org` (free public Coinbase endpoint,
570
+ * 2k-block cap; last-resort backup).
571
+ * Operators are encouraged to prepend a paid primary key (Alchemy, Tenderly,
572
+ * etc.) via `rpcUrl` config or `JINN_RPC_URL` / `BASE_SEPOLIA_RPC_URL` env.
573
+ */
574
+ export const DEFAULT_TESTNET_RPC_URLS = [
575
+ 'https://base-sepolia.publicnode.com',
576
+ 'https://sepolia.base.org',
577
+ ];
560
578
  export class ConfigLoadError extends Error {
561
579
  code;
562
580
  details;
@@ -567,7 +585,93 @@ export class ConfigLoadError extends Error {
567
585
  this.details = details;
568
586
  }
569
587
  }
570
- // ── Loader ──────────────────────────────────────────────────────────────────
588
+ /**
589
+ * Parse a legacy `<id>.<version>` solverType string into `{id, version}`.
590
+ * Falls back to `{ id: fallbackId, version: 'v1' }` when the string lacks a
591
+ * dot or terminates in one — this happens only on hand-edited operator
592
+ * configs and keeps the migration loud-but-non-fatal.
593
+ */
594
+ function parseSolverTypeRef(solverType, fallbackId) {
595
+ if (typeof solverType !== 'string') {
596
+ return { id: fallbackId, version: 'v1' };
597
+ }
598
+ const dot = solverType.lastIndexOf('.');
599
+ if (dot <= 0 || dot === solverType.length - 1) {
600
+ return { id: fallbackId, version: 'v1' };
601
+ }
602
+ return { id: solverType.slice(0, dot), version: solverType.slice(dot + 1) };
603
+ }
604
+ /**
605
+ * Translate any legacy short-name-keyed `solverNets` block on the raw parsed
606
+ * config into manifest-keyed `joinedSolverNets` entries with synthetic
607
+ * `legacy:<short-name>` keys.
608
+ *
609
+ * This is the auto-migration path for operators upgrading past issue #421.
610
+ * The runtime claim filter remains manifest-digest gated, so synthetic-keyed
611
+ * entries don't change task eligibility — they exist purely so the diagnostic
612
+ * surfaces (Overview SOLVING-ON eyebrow, prediction-operator-status) keep
613
+ * showing the operator's previous SolverNets until they re-join via the SPA.
614
+ *
615
+ * Returns the number of legacy entries migrated. Idempotent — calling on an
616
+ * already-migrated raw config is a no-op.
617
+ *
618
+ * @param raw — the JSON-parsed config file contents (mutated in place).
619
+ */
620
+ export function migrateLegacySolverNets(raw) {
621
+ const legacy = raw['solverNets'];
622
+ if (!legacy || typeof legacy !== 'object' || Array.isArray(legacy)) {
623
+ return 0;
624
+ }
625
+ const entries = Object.entries(legacy);
626
+ if (entries.length === 0) {
627
+ delete raw['solverNets'];
628
+ return 0;
629
+ }
630
+ const joined = (typeof raw['joinedSolverNets'] === 'object' && raw['joinedSolverNets'] !== null && !Array.isArray(raw['joinedSolverNets']))
631
+ ? raw['joinedSolverNets']
632
+ : {};
633
+ let migrated = 0;
634
+ for (const [name, entryRaw] of entries) {
635
+ if (!entryRaw || typeof entryRaw !== 'object')
636
+ continue;
637
+ const entry = entryRaw;
638
+ const syntheticKey = `legacy:${name}`;
639
+ // Do not overwrite a pre-existing joinedSolverNets entry under the same key.
640
+ if (joined[syntheticKey] !== undefined)
641
+ continue;
642
+ const contract = parseSolverTypeRef(entry.solverType, name);
643
+ const rolesIn = Array.isArray(entry.roles) && entry.roles.length > 0
644
+ ? entry.roles
645
+ : ['solving'];
646
+ const roles = [];
647
+ for (const r of rolesIn) {
648
+ if (r === 'solving')
649
+ roles.push('solver');
650
+ else if (r === 'evaluating')
651
+ roles.push('evaluator');
652
+ // 'launching' (and any other unknown role) is dropped — operator config
653
+ // no longer carries the launcher role per spec §11.
654
+ }
655
+ if (roles.length === 0)
656
+ roles.push('solver');
657
+ joined[syntheticKey] = {
658
+ manifestCid: syntheticKey,
659
+ name,
660
+ contract,
661
+ roles: Array.from(new Set(roles)),
662
+ ...(typeof entry.harness === 'string' ? { harness: entry.harness } : {}),
663
+ ...(typeof entry.model === 'string' ? { model: entry.model } : {}),
664
+ plugins: Array.isArray(entry.plugins) ? entry.plugins : [],
665
+ disabledDefaultPlugins: [],
666
+ };
667
+ migrated += 1;
668
+ }
669
+ if (Object.keys(joined).length > 0) {
670
+ raw['joinedSolverNets'] = joined;
671
+ }
672
+ delete raw['solverNets'];
673
+ return migrated;
674
+ }
571
675
  /**
572
676
  * Load config with resolution: env > config file > defaults.
573
677
  *
@@ -614,6 +718,11 @@ export function loadConfig(configPath) {
614
718
  }
615
719
  if (env['JINN_BALANCE_TOPUP_INTERVAL_MS'])
616
720
  merged.balanceTopupIntervalMs = Number.parseInt(env['JINN_BALANCE_TOPUP_INTERVAL_MS'], 10);
721
+ if (env['JINN_EVICTION_CHECK_INTERVAL_MS'])
722
+ merged.evictionCheckIntervalMs = Number.parseInt(env['JINN_EVICTION_CHECK_INTERVAL_MS'], 10);
723
+ if (env['JINN_CHECKPOINT_INTERVAL_MS'] !== undefined) {
724
+ merged.checkpointIntervalMs = Number.parseInt(env['JINN_CHECKPOINT_INTERVAL_MS'], 10);
725
+ }
617
726
  if (env['JINN_API_PORT'])
618
727
  merged.apiPort = parseInt(env['JINN_API_PORT'], 10);
619
728
  if (env['JINN_API_BIND_HOST'])
@@ -631,6 +740,11 @@ export function loadConfig(configPath) {
631
740
  if (env['JINN_HERMES_DOCTOR_TIMEOUT_MS']) {
632
741
  merged.hermesDoctorTimeoutMs = parseInt(env['JINN_HERMES_DOCTOR_TIMEOUT_MS'], 10);
633
742
  }
743
+ if (env['JINN_CODEX_PATH'])
744
+ merged.codexPath = env['JINN_CODEX_PATH'];
745
+ if (env['JINN_CODEX_DOCTOR_TIMEOUT_MS']) {
746
+ merged.codexDoctorTimeoutMs = parseInt(env['JINN_CODEX_DOCTOR_TIMEOUT_MS'], 10);
747
+ }
634
748
  if (env['JINN_RUNTIME_MODE'])
635
749
  merged.runtimeMode = env['JINN_RUNTIME_MODE'];
636
750
  if (env['JINN_PEERS'])
@@ -647,12 +761,14 @@ export function loadConfig(configPath) {
647
761
  // A URL only makes sense in http mode — when the operator points
648
762
  // JINN_DISCOVERY_URL at a host but doesn't say JINN_DISCOVERY_MODE,
649
763
  // default mode to 'http' so the URL is actually consulted (and isn't
650
- // silently dropped by the on-chain default in createDiscoveryAPI). In that
651
- // inferred-http case, also default fallbackToOnchain on (http without a
652
- // floor is a footgun) unless JINN_DISCOVERY_FALLBACK overrides.
764
+ // silently dropped by the on-chain default in createDiscoveryAPI).
765
+ // `fallbackToOnchain` is NOT defaulted on here (since the 2026-05-23
766
+ // substrate incident): silent fall-through hides indexer outages and
767
+ // storms shared RPC. Operators opt in via JINN_DISCOVERY_FALLBACK=1 or
768
+ // the config file when they want it.
653
769
  const inferredHttp = !!env['JINN_DISCOVERY_URL'] && !env['JINN_DISCOVERY_MODE'] && !prevDiscovery['mode'];
654
770
  const mode = env['JINN_DISCOVERY_MODE'] ?? (inferredHttp ? 'http' : undefined);
655
- const resolvedFallback = fallbackToOnchain ?? (inferredHttp ? true : undefined);
771
+ const resolvedFallback = fallbackToOnchain;
656
772
  merged['discovery'] = {
657
773
  ...prevDiscovery,
658
774
  ...(mode ? { mode } : {}),
@@ -690,6 +806,13 @@ export function loadConfig(configPath) {
690
806
  merged.jinnMessengerAddress = env['JINN_MESSENGER_ADDRESS'];
691
807
  if (env['JINN_MESSENGER_MODE'])
692
808
  merged.jinnMessengerMode = env['JINN_MESSENGER_MODE'];
809
+ if (env['JINN_CLAIM_SUBMISSION_MODE']) {
810
+ merged.jinnClaimSubmissionMode = env['JINN_CLAIM_SUBMISSION_MODE'];
811
+ }
812
+ if (env['JINN_CLAIM_LOOP_ENABLED'] !== undefined) {
813
+ const v = env['JINN_CLAIM_LOOP_ENABLED'].trim().toLowerCase();
814
+ merged.jinnClaimLoopEnabled = v === '1' || v === 'true' || v === 'yes';
815
+ }
693
816
  if (env['JINN_CLAIM_LOOP_INTERVAL_MS'] !== undefined) {
694
817
  merged.jinnClaimLoopIntervalMs = parseInt(env['JINN_CLAIM_LOOP_INTERVAL_MS'], 10);
695
818
  }
@@ -787,7 +910,13 @@ export function loadConfig(configPath) {
787
910
  const resolvedNetwork = merged.network === 'testnet' ? 'testnet' : 'mainnet';
788
911
  // Testnet default: point discovery at the privately-operated Ponder indexer
789
912
  // (jinn-mono-280n.4), unless the operator has set their own `discovery` block.
790
- // The on-chain RPC floor stays as the fallback.
913
+ //
914
+ // `fallbackToOnchain` is NOT defaulted on (since the 2026-05-23 substrate
915
+ // incident): silent fall-through to direct eth_getLogs hides indexer outages
916
+ // and turns every daemon into its own indexer, which storms shared RPC
917
+ // quota and can take the indexer down. Operators opt in explicitly when
918
+ // they need it (typically only when self-hosting an RPC with generous
919
+ // getLogs quotas).
791
920
  //
792
921
  // Only fill fields the operator left absent — never overwrite an
793
922
  // operator-set `url` / `mode` / `fallbackToOnchain`. A bare
@@ -804,9 +933,17 @@ export function loadConfig(configPath) {
804
933
  ...(existing ?? {}),
805
934
  mode: existing?.mode ?? 'http',
806
935
  url: existing?.url ?? DEFAULT_TESTNET_DISCOVERY_URL,
807
- fallbackToOnchain: existing?.fallbackToOnchain ?? true,
936
+ ...(existing?.fallbackToOnchain !== undefined
937
+ ? { fallbackToOnchain: existing.fallbackToOnchain }
938
+ : {}),
808
939
  };
809
940
  }
941
+ if (resolvedNetwork === 'testnet' && !merged.ethereumRpcUrl) {
942
+ merged.ethereumRpcUrl = DEFAULT_TESTNET_ETHEREUM_RPC_URL;
943
+ }
944
+ if (resolvedNetwork === 'testnet' && merged.jinnClaimLoopEnabled === undefined) {
945
+ merged.jinnClaimLoopEnabled = true;
946
+ }
810
947
  // Keep the legacy BASE_RPC_URL override for Base mainnet only. Testnet must
811
948
  // not silently inherit a mainnet RPC from client/.env during bootstrap.
812
949
  if (env['JINN_RPC_URL']) {
@@ -839,6 +976,17 @@ export function loadConfig(configPath) {
839
976
  });
840
977
  }
841
978
  }
979
+ // Auto-migrate any legacy short-name-keyed `solverNets` block into
980
+ // `joinedSolverNets` with synthetic `legacy:<name>` keys. Operators upgrade
981
+ // without an explicit action; the warning surfaces the migration so they
982
+ // know to re-join via the SPA when they want a real manifest CID. See
983
+ // spec/2026-05-25-retire-legacy-solvernets-config.md and issue #421.
984
+ const migratedCount = migrateLegacySolverNets(merged);
985
+ if (migratedCount > 0) {
986
+ console.warn(`[config] Migrated ${migratedCount} legacy solverNets ${migratedCount === 1 ? 'entry' : 'entries'} to joinedSolverNets. ` +
987
+ 'Open Operator > SolverNets in the dashboard to re-join via the registry ' +
988
+ '(replaces the synthetic legacy:* keys with real manifest CIDs).');
989
+ }
842
990
  // 3. Validate
843
991
  const result = JinnConfigSchema.safeParse(merged);
844
992
  if (!result.success) {
@@ -850,17 +998,56 @@ export function loadConfig(configPath) {
850
998
  });
851
999
  }
852
1000
  // 4. Resolve rpcUrl default based on network (if not explicitly set).
853
- // Testnet default is a Tenderly gateway (free public key) much higher
854
- // rate limits than `https://sepolia.base.org`. See contracts.ts comment +
855
- // the panel's "shared RPC" warning in NetworkSection.tsx for the operator-
856
- // facing pitch to bring their own key.
1001
+ // Testnet default per AC2 (issue #592) is a two-provider fallback chain:
1002
+ // slot 0 base-sepolia.publicnode.com (50k-block getLogs cap, no quota)
1003
+ // slot 1 sepolia.base.org (free, 2k-block cap; last-resort backup)
1004
+ // Publicnode is no-auth, no shared-key quota cliff (avoids the Tenderly
1005
+ // shared-quota cliff of 2026-05-24 that took out every default-config
1006
+ // daemon at once). See #554 + the NetworkSection.tsx "shared RPC" panel for
1007
+ // the operator-facing pitch to bring their own key. The sibling Ethereum L1
1008
+ // default (DEFAULT_TESTNET_ETHEREUM_RPC_URL above) is already publicnode —
1009
+ // this keeps the two L1/L2 defaults symmetric.
857
1010
  const parsed = result.data;
858
- const defaultRpcUrl = parsed.network === 'testnet'
859
- ? 'https://base-sepolia.gateway.tenderly.co/75tyLMQuD8EHpXxMwINIKu'
860
- : 'https://mainnet.base.org';
1011
+ const defaultRpcUrls = parsed.network === 'testnet'
1012
+ ? DEFAULT_TESTNET_RPC_URLS
1013
+ : ['https://mainnet.base.org'];
1014
+ const rpcUrlsResolved = parsed.rpcUrl !== undefined
1015
+ ? parseRpcUrls(parsed.rpcUrl)
1016
+ : [...defaultRpcUrls];
1017
+ const archiveRpcUrlsResolved = parsed.archiveRpcUrl !== undefined
1018
+ ? parseRpcUrls(parsed.archiveRpcUrl)
1019
+ : undefined;
1020
+ const l2ProofRpcUrlsResolved = parsed.l2ProofRpcUrl !== undefined
1021
+ ? parseRpcUrls(parsed.l2ProofRpcUrl)
1022
+ : undefined;
1023
+ const ethereumRpcUrlsResolved = parsed.ethereumRpcUrl !== undefined
1024
+ ? parseRpcUrls(parsed.ethereumRpcUrl)
1025
+ : undefined;
1026
+ const ethereumArchiveRpcUrlsResolved = parsed.ethereumArchiveRpcUrl !== undefined
1027
+ ? parseRpcUrls(parsed.ethereumArchiveRpcUrl)
1028
+ : undefined;
1029
+ // Strip the union-typed (string | string[]) RPC fields from `parsed` — the
1030
+ // returned shape carries the resolved head URL plus a `*Urls` array instead.
1031
+ const { rpcUrl: _rpcUrl, archiveRpcUrl: _archiveRpcUrl, l2ProofRpcUrl: _l2ProofRpcUrl, ethereumRpcUrl: _ethereumRpcUrl, ethereumArchiveRpcUrl: _ethereumArchiveRpcUrl, ...rest } = parsed;
861
1032
  return {
862
- ...parsed,
863
- rpcUrl: parsed.rpcUrl ?? defaultRpcUrl,
1033
+ ...rest,
1034
+ rpcUrl: rpcUrlsResolved[0],
1035
+ rpcUrls: rpcUrlsResolved,
1036
+ ...(archiveRpcUrlsResolved
1037
+ ? { archiveRpcUrl: archiveRpcUrlsResolved[0], archiveRpcUrls: archiveRpcUrlsResolved }
1038
+ : {}),
1039
+ ...(l2ProofRpcUrlsResolved
1040
+ ? { l2ProofRpcUrl: l2ProofRpcUrlsResolved[0], l2ProofRpcUrls: l2ProofRpcUrlsResolved }
1041
+ : {}),
1042
+ ...(ethereumRpcUrlsResolved
1043
+ ? { ethereumRpcUrl: ethereumRpcUrlsResolved[0], ethereumRpcUrls: ethereumRpcUrlsResolved }
1044
+ : {}),
1045
+ ...(ethereumArchiveRpcUrlsResolved
1046
+ ? {
1047
+ ethereumArchiveRpcUrl: ethereumArchiveRpcUrlsResolved[0],
1048
+ ethereumArchiveRpcUrls: ethereumArchiveRpcUrlsResolved,
1049
+ }
1050
+ : {}),
864
1051
  // parseTask assigns a UUID to any entry missing an id
865
1052
  tasks: parsed.tasks.map(parseTask),
866
1053
  engine: {
@@ -907,6 +1094,7 @@ const TRACKED_ENV_VARS = [
907
1094
  'JINN_DB_PATH',
908
1095
  'JINN_POLL_INTERVAL_MS',
909
1096
  'JINN_REWARD_CLAIM_INTERVAL_MS',
1097
+ 'JINN_CHECKPOINT_INTERVAL_MS',
910
1098
  'JINN_BALANCE_TOPUP_INTERVAL_MS',
911
1099
  'JINN_API_PORT',
912
1100
  'JINN_API_BIND_HOST',
@@ -937,6 +1125,8 @@ const TRACKED_ENV_VARS = [
937
1125
  'JINN_CLAIM_EMITTER_ADDRESS',
938
1126
  'JINN_MESSENGER_ADDRESS',
939
1127
  'JINN_MESSENGER_MODE',
1128
+ 'JINN_CLAIM_SUBMISSION_MODE',
1129
+ 'JINN_CLAIM_LOOP_ENABLED',
940
1130
  'JINN_CLAIM_LOOP_INTERVAL_MS',
941
1131
  'JINN_STAKING_MODE',
942
1132
  'JINN_TARGET_SERVICES',
@@ -961,6 +1151,7 @@ const TRACKED_ENV_VARS = [
961
1151
  'JINN_CAPTURES_LLM_PROXY_ENABLED',
962
1152
  'JINN_CAPTURES_LLM_PROXY_PORT',
963
1153
  'JINN_BUILD_COMMIT',
1154
+ 'JINN_SPEND_CAP_USD',
964
1155
  ];
965
1156
  /**
966
1157
  * Build a structured provenance block describing how the config was resolved.