xytara 3.1.16 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. package/README.md +120 -92
  2. package/RELEASE_NOTES.md +7 -3
  3. package/START_HERE.md +56 -224
  4. package/bin/xytara.js +21 -8
  5. package/client.js +79 -1
  6. package/lib/{delivery_receipt.js → delivery_receipt_verify.js} +14 -100
  7. package/package.json +25 -35
  8. package/scripts/verify_client.js +418 -0
  9. package/scripts/verify_release_candidate.js +146 -45
  10. package/.env.example +0 -173
  11. package/ADAPTER_START_HERE.md +0 -56
  12. package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +0 -126
  13. package/CARRIED_HANDOFF_DEMO.md +0 -60
  14. package/EXAMPLE_PATHS.md +0 -53
  15. package/FINAL_CONTRACT.md +0 -498
  16. package/PARTNER_READY_PATH.md +0 -71
  17. package/PUBLIC_DEMO_SCRIPT.md +0 -78
  18. package/REAL_PAYMENT_SETUP.md +0 -320
  19. package/SERVICE_CONTRACT.md +0 -419
  20. package/SUPABASE_RUNTIME_STATE_SETUP.md +0 -61
  21. package/TERMINOLOGY.md +0 -50
  22. package/WHY_XYTARA_XOONYA.md +0 -63
  23. package/XT1_EXECUTION_TRUTH_STANDARD.md +0 -78
  24. package/XYTARA_ADAPTER_PROTOCOL_TRUTH_MODEL.md +0 -75
  25. package/XYTARA_AUTONOMOUS_ENTRY_MODEL.md +0 -124
  26. package/XYTARA_DEFAULT_PATH_MODEL.md +0 -84
  27. package/XYTARA_EXECUTION_TRUTH_MODEL.md +0 -171
  28. package/XYTARA_PAYMENT_RAIL_TRUTH_MODEL.md +0 -68
  29. package/adapters/AUTHORING_GUIDE.md +0 -244
  30. package/adapters/ONBOARDING_POSTURE.md +0 -61
  31. package/adapters/THIRD_PARTY_SUBMISSION_PROCESS.md +0 -84
  32. package/adapters/adapter-manifest.schema.json +0 -283
  33. package/adapters/examples/example-execution-adapter.js +0 -61
  34. package/adapters/examples/example-execution-adapter.manifest.json +0 -81
  35. package/adapters/examples/example-settlement-adapter.js +0 -55
  36. package/adapters/examples/example-settlement-adapter.manifest.json +0 -78
  37. package/adapters/examples/example-third-party-acp-registration.submission-bundle.json +0 -124
  38. package/adapters/examples/example-third-party-erc8004-registration.submission-bundle.json +0 -121
  39. package/adapters/examples/example-third-party-identity-registration.certification-pack.json +0 -22
  40. package/adapters/examples/example-third-party-identity-registration.record.json +0 -26
  41. package/adapters/examples/example-third-party-identity-registration.submission-bundle.json +0 -126
  42. package/adapters/examples/example-third-party-langchain-registration.submission-bundle.json +0 -124
  43. package/adapters/examples/example-third-party-mpp-registration.submission-bundle.json +0 -120
  44. package/adapters/examples/example-third-party-registration.certification-pack.json +0 -22
  45. package/adapters/examples/example-third-party-registration.record.json +0 -28
  46. package/adapters/examples/example-third-party-registration.submission-bundle.json +0 -126
  47. package/adapters/examples/example-third-party-settlement-registration.certification-pack.json +0 -22
  48. package/adapters/examples/example-third-party-settlement-registration.record.json +0 -26
  49. package/adapters/examples/example-third-party-settlement-registration.submission-bundle.json +0 -127
  50. package/adapters/examples/langchain-reference-execution-adapter.js +0 -81
  51. package/adapters/examples/langchain-reference-execution-adapter.manifest.json +0 -79
  52. package/adapters/examples/langgraph-reference-execution-adapter.js +0 -81
  53. package/adapters/examples/langgraph-reference-execution-adapter.manifest.json +0 -79
  54. package/adapters/examples/minimal-third-party-execution-adapter.js +0 -61
  55. package/adapters/examples/minimal-third-party-execution-adapter.manifest.json +0 -79
  56. package/adapters/examples/minimal-third-party-execution-registration.record.json +0 -26
  57. package/adapters/examples/minimal-third-party-execution.certification-pack.json +0 -22
  58. package/adapters/examples/minimal-third-party-execution.submission-bundle.json +0 -126
  59. package/adapters/integration-certification-pack.schema.json +0 -82
  60. package/adapters/integration-registration-record.schema.json +0 -114
  61. package/adapters/integration-submission-bundle-set.schema.json +0 -39
  62. package/adapters/integration-submission-bundle.schema.json +0 -34
  63. package/adapters/interfaces.js +0 -56
  64. package/bin/xytara-release.js +0 -1429
  65. package/examples/_demo_env.js +0 -40
  66. package/examples/adapter_review_walkthrough.js +0 -54
  67. package/examples/export_carried_handoff.js +0 -81
  68. package/examples/funded_runtime_walkthrough.js +0 -71
  69. package/examples/partner_launch_walkthrough.js +0 -44
  70. package/examples/quickstart.js +0 -3256
  71. package/fixtures/catalog.js +0 -1156
  72. package/index.js +0 -395
  73. package/integrations/dispatch.js +0 -276
  74. package/integrations/registry-snapshot.schema.json +0 -36
  75. package/integrations/registry.js +0 -2261
  76. package/integrations/views.js +0 -28
  77. package/lib/a2a_lane_contract.js +0 -104
  78. package/lib/a2a_rpc.js +0 -287
  79. package/lib/a2c_lane_contract.js +0 -111
  80. package/lib/account_auth.js +0 -585
  81. package/lib/acp_lane_contract.js +0 -54
  82. package/lib/activity_audit_contract.js +0 -322
  83. package/lib/adapter_certification_contract.js +0 -130
  84. package/lib/adapter_depth_contract.js +0 -218
  85. package/lib/adapter_pack.js +0 -118
  86. package/lib/adapter_partner_pack.js +0 -89
  87. package/lib/adapter_promotion_pack.js +0 -89
  88. package/lib/adapter_protocol_truth_contract.js +0 -229
  89. package/lib/agent_identity.js +0 -162
  90. package/lib/announcement_pack.js +0 -61
  91. package/lib/artifact_distribution_lane_contract.js +0 -54
  92. package/lib/asyncapi_contract.js +0 -150
  93. package/lib/auth_interop_contract.js +0 -80
  94. package/lib/authority_binding_lifecycle_contract.js +0 -182
  95. package/lib/autonomous_entry_contract.js +0 -409
  96. package/lib/bolt_adapter.js +0 -187
  97. package/lib/bolt_lane_contract.js +0 -55
  98. package/lib/bolt_payment_front.js +0 -224
  99. package/lib/business_cockpit.js +0 -378
  100. package/lib/buyer_event_log.js +0 -230
  101. package/lib/buyer_safety_policy.js +0 -132
  102. package/lib/capability_execution_truth.js +0 -276
  103. package/lib/capability_registry.js +0 -633
  104. package/lib/catalog_contract.js +0 -966
  105. package/lib/catalog_family_model.js +0 -193
  106. package/lib/catalog_views.js +0 -55
  107. package/lib/cdp_jwt.js +0 -57
  108. package/lib/checkout_event_security.js +0 -142
  109. package/lib/checkout_processor_adapter.js +0 -217
  110. package/lib/cloudevents_contract.js +0 -80
  111. package/lib/command_flow.js +0 -911
  112. package/lib/commerce_artifacts.js +0 -591
  113. package/lib/commerce_authority.js +0 -449
  114. package/lib/commerce_bridge.js +0 -274
  115. package/lib/commerce_cases.js +0 -399
  116. package/lib/commerce_checkout.js +0 -509
  117. package/lib/commerce_coordination.js +0 -346
  118. package/lib/commerce_economics.js +0 -4017
  119. package/lib/commerce_identity.js +0 -578
  120. package/lib/commerce_quotes.js +0 -266
  121. package/lib/commerce_reports.js +0 -14461
  122. package/lib/commerce_runtime.js +0 -322
  123. package/lib/commerce_shell.js +0 -257
  124. package/lib/crypto_agility_matrix.js +0 -125
  125. package/lib/default_path_contract.js +0 -412
  126. package/lib/delivery_summary_contract.js +0 -95
  127. package/lib/ecosystem_entry.js +0 -64
  128. package/lib/erc8004_lane_contract.js +0 -65
  129. package/lib/event_system_lane_contract.js +0 -75
  130. package/lib/execution_dedupe.js +0 -86
  131. package/lib/execution_truth_contract.js +0 -234
  132. package/lib/external_credit_bridge.js +0 -142
  133. package/lib/external_execution_policy.js +0 -143
  134. package/lib/external_execution_runtime.js +0 -238
  135. package/lib/feature_control_lane_contract.js +0 -54
  136. package/lib/first_run_conversion_contract.js +0 -290
  137. package/lib/framework_lane_contract.js +0 -89
  138. package/lib/framework_provider_promotion.js +0 -235
  139. package/lib/full_catalog_contract.js +0 -525
  140. package/lib/go_live_operator_pack.js +0 -391
  141. package/lib/http_transport.js +0 -99
  142. package/lib/identity_auth.js +0 -175
  143. package/lib/identity_interop_contract.js +0 -82
  144. package/lib/integration_matrix_contract.js +0 -102
  145. package/lib/l402_adapter.js +0 -241
  146. package/lib/l402_lane_contract.js +0 -55
  147. package/lib/l402_payment_front.js +0 -265
  148. package/lib/launch_narrative.js +0 -53
  149. package/lib/lifecycle_continuity_contract.js +0 -115
  150. package/lib/machine_identity_registry_contract.js +0 -199
  151. package/lib/major_rails_lane_contract.js +0 -150
  152. package/lib/market_lane_contract.js +0 -467
  153. package/lib/market_lane_depth_contract.js +0 -182
  154. package/lib/market_lane_promotion_contract.js +0 -210
  155. package/lib/market_lane_trace_contract.js +0 -278
  156. package/lib/mcp_lane_contract.js +0 -137
  157. package/lib/mpp_adapter.js +0 -237
  158. package/lib/network_transport.js +0 -110
  159. package/lib/offline_verifiability_recovery_contract.js +0 -155
  160. package/lib/openapi_contract.js +0 -300
  161. package/lib/operational_receipt_contract.js +0 -165
  162. package/lib/operator_intelligence.js +0 -407
  163. package/lib/outreach_copy_pack.js +0 -51
  164. package/lib/outreach_message_pack.js +0 -71
  165. package/lib/outreach_proof.js +0 -71
  166. package/lib/outreach_target_pack.js +0 -60
  167. package/lib/partner_intelligence.js +0 -105
  168. package/lib/payment_config.js +0 -257
  169. package/lib/payment_front_registry.js +0 -53
  170. package/lib/payment_protocol_contract.js +0 -165
  171. package/lib/payment_rail_truth_contract.js +0 -324
  172. package/lib/payment_verification.js +0 -357
  173. package/lib/phase_10_closeout_pack.js +0 -45
  174. package/lib/phase_10_completion_pack.js +0 -76
  175. package/lib/phase_10_decision_record_pack.js +0 -54
  176. package/lib/phase_10_decision_resolution_pack.js +0 -53
  177. package/lib/phase_10_demand_signal_adapters_runtime_pack.js +0 -74
  178. package/lib/phase_10_ecosystem_backlog_triage_runtime_pack.js +0 -74
  179. package/lib/phase_10_freeze_baseline_pack.js +0 -67
  180. package/lib/phase_10_freeze_review_pack.js +0 -64
  181. package/lib/phase_10_gate_pack.js +0 -57
  182. package/lib/phase_10_long_tail_continuity_pack.js +0 -80
  183. package/lib/phase_10_long_tail_ecosystem_pack.js +0 -66
  184. package/lib/phase_10_niche_extension_registry_runtime_pack.js +0 -74
  185. package/lib/phase_1_claude_mcp_pack.js +0 -60
  186. package/lib/phase_1_claude_mcp_runtime_pack.js +0 -52
  187. package/lib/phase_1_ecosystem_pack.js +0 -67
  188. package/lib/phase_1_openai_codex_pack.js +0 -60
  189. package/lib/phase_1_openai_codex_runtime_pack.js +0 -52
  190. package/lib/phase_2_base_runtime_pack.js +0 -71
  191. package/lib/phase_2_base_usdc_runtime_pack.js +0 -72
  192. package/lib/phase_2_closeout_pack.js +0 -65
  193. package/lib/phase_2_completion_pack.js +0 -113
  194. package/lib/phase_2_decision_record_pack.js +0 -66
  195. package/lib/phase_2_decision_resolution_pack.js +0 -69
  196. package/lib/phase_2_eth_runtime_pack.js +0 -70
  197. package/lib/phase_2_evm_base_runtime_pack.js +0 -73
  198. package/lib/phase_2_evm_runtime_pack.js +0 -71
  199. package/lib/phase_2_flip_preview_pack.js +0 -60
  200. package/lib/phase_2_freeze_review_pack.js +0 -99
  201. package/lib/phase_2_gate_pack.js +0 -77
  202. package/lib/phase_2_payment_rails_pack.js +0 -71
  203. package/lib/phase_2_usdc_runtime_pack.js +0 -71
  204. package/lib/phase_3_brc_runtime_pack.js +0 -81
  205. package/lib/phase_3_bsv_teranode_runtime_pack.js +0 -83
  206. package/lib/phase_3_closeout_pack.js +0 -62
  207. package/lib/phase_3_completion_pack.js +0 -94
  208. package/lib/phase_3_decision_record_pack.js +0 -64
  209. package/lib/phase_3_decision_resolution_pack.js +0 -69
  210. package/lib/phase_3_freeze_baseline_pack.js +0 -67
  211. package/lib/phase_3_freeze_review_pack.js +0 -80
  212. package/lib/phase_3_gate_pack.js +0 -76
  213. package/lib/phase_3_metanet_runtime_pack.js +0 -81
  214. package/lib/phase_3_native_bsv_pack.js +0 -91
  215. package/lib/phase_3_native_continuity_pack.js +0 -82
  216. package/lib/phase_4_attestation_runtime_pack.js +0 -75
  217. package/lib/phase_4_closeout_pack.js +0 -45
  218. package/lib/phase_4_completion_pack.js +0 -94
  219. package/lib/phase_4_decision_record_pack.js +0 -54
  220. package/lib/phase_4_decision_resolution_pack.js +0 -45
  221. package/lib/phase_4_erc8004_runtime_pack.js +0 -76
  222. package/lib/phase_4_freeze_baseline_pack.js +0 -67
  223. package/lib/phase_4_freeze_review_pack.js +0 -68
  224. package/lib/phase_4_gate_pack.js +0 -58
  225. package/lib/phase_4_identity_registry_runtime_pack.js +0 -75
  226. package/lib/phase_4_identity_trust_pack.js +0 -73
  227. package/lib/phase_4_trust_continuity_pack.js +0 -86
  228. package/lib/phase_5_antelope_runtime_pack.js +0 -76
  229. package/lib/phase_5_closeout_pack.js +0 -44
  230. package/lib/phase_5_completion_pack.js +0 -82
  231. package/lib/phase_5_constellation_dag_runtime_pack.js +0 -76
  232. package/lib/phase_5_decision_record_pack.js +0 -54
  233. package/lib/phase_5_decision_resolution_pack.js +0 -45
  234. package/lib/phase_5_digibyte_dgb_runtime_pack.js +0 -76
  235. package/lib/phase_5_freeze_baseline_pack.js +0 -67
  236. package/lib/phase_5_freeze_review_pack.js +0 -68
  237. package/lib/phase_5_gate_pack.js +0 -58
  238. package/lib/phase_5_hedera_hbar_runtime_pack.js +0 -76
  239. package/lib/phase_5_kaspa_kas_runtime_pack.js +0 -76
  240. package/lib/phase_5_major_rails_continuity_pack.js +0 -86
  241. package/lib/phase_5_major_rails_pack.js +0 -110
  242. package/lib/phase_5_metal_runtime_pack.js +0 -76
  243. package/lib/phase_5_polkadot_dot_runtime_pack.js +0 -76
  244. package/lib/phase_5_proton_xpr_runtime_pack.js +0 -76
  245. package/lib/phase_5_solana_runtime_pack.js +0 -76
  246. package/lib/phase_6_autogen_runtime_pack.js +0 -72
  247. package/lib/phase_6_closeout_pack.js +0 -46
  248. package/lib/phase_6_completion_pack.js +0 -77
  249. package/lib/phase_6_decision_record_pack.js +0 -54
  250. package/lib/phase_6_decision_resolution_pack.js +0 -53
  251. package/lib/phase_6_framework_continuity_pack.js +0 -81
  252. package/lib/phase_6_framework_runtime_pack.js +0 -68
  253. package/lib/phase_6_freeze_baseline_pack.js +0 -68
  254. package/lib/phase_6_freeze_review_pack.js +0 -65
  255. package/lib/phase_6_gate_pack.js +0 -59
  256. package/lib/phase_6_langchain_runtime_pack.js +0 -72
  257. package/lib/phase_6_langgraph_runtime_pack.js +0 -72
  258. package/lib/phase_6_semantic_kernel_runtime_pack.js +0 -72
  259. package/lib/phase_7_closeout_pack.js +0 -45
  260. package/lib/phase_7_completion_pack.js +0 -85
  261. package/lib/phase_7_decision_record_pack.js +0 -53
  262. package/lib/phase_7_decision_resolution_pack.js +0 -53
  263. package/lib/phase_7_event_system_continuity_pack.js +0 -89
  264. package/lib/phase_7_event_system_pack.js +0 -76
  265. package/lib/phase_7_freeze_baseline_pack.js +0 -74
  266. package/lib/phase_7_freeze_review_pack.js +0 -65
  267. package/lib/phase_7_gate_pack.js +0 -58
  268. package/lib/phase_7_kafka_runtime_pack.js +0 -74
  269. package/lib/phase_7_mqtt_runtime_pack.js +0 -74
  270. package/lib/phase_7_nats_runtime_pack.js +0 -74
  271. package/lib/phase_7_webhook_event_bus_runtime_pack.js +0 -74
  272. package/lib/phase_8_closeout_pack.js +0 -46
  273. package/lib/phase_8_completion_pack.js +0 -82
  274. package/lib/phase_8_decision_record_pack.js +0 -54
  275. package/lib/phase_8_decision_resolution_pack.js +0 -53
  276. package/lib/phase_8_external_receipt_import_runtime_pack.js +0 -74
  277. package/lib/phase_8_external_result_import_runtime_pack.js +0 -74
  278. package/lib/phase_8_freeze_baseline_pack.js +0 -71
  279. package/lib/phase_8_freeze_review_pack.js +0 -64
  280. package/lib/phase_8_gate_pack.js +0 -58
  281. package/lib/phase_8_proof_bridge_continuity_pack.js +0 -86
  282. package/lib/phase_8_proof_bridges_pack.js +0 -72
  283. package/lib/phase_8_proof_bundle_normalization_runtime_pack.js +0 -74
  284. package/lib/phase_9_closeout_pack.js +0 -46
  285. package/lib/phase_9_completion_pack.js +0 -82
  286. package/lib/phase_9_custody_refs_runtime_pack.js +0 -74
  287. package/lib/phase_9_decision_record_pack.js +0 -54
  288. package/lib/phase_9_decision_resolution_pack.js +0 -53
  289. package/lib/phase_9_freeze_baseline_pack.js +0 -71
  290. package/lib/phase_9_freeze_review_pack.js +0 -64
  291. package/lib/phase_9_gate_pack.js +0 -58
  292. package/lib/phase_9_operator_accounting_bridges_runtime_pack.js +0 -74
  293. package/lib/phase_9_treasury_connectivity_pack.js +0 -72
  294. package/lib/phase_9_treasury_continuity_pack.js +0 -86
  295. package/lib/phase_9_treasury_destinations_runtime_pack.js +0 -74
  296. package/lib/phase_program_pack.js +0 -120
  297. package/lib/pricing_optimization_contract.js +0 -775
  298. package/lib/protocol_lane_contract.js +0 -117
  299. package/lib/provenance_lane_contract.js +0 -54
  300. package/lib/provider_lane_contract.js +0 -240
  301. package/lib/provider_network_contract.js +0 -149
  302. package/lib/provider_payout_execution.js +0 -155
  303. package/lib/provider_registry.js +0 -852
  304. package/lib/provider_runtime_journey_contract.js +0 -183
  305. package/lib/public_surface_policy_contract.js +0 -111
  306. package/lib/publish_plan.js +0 -51
  307. package/lib/rate_limiter.js +0 -81
  308. package/lib/record_queries.js +0 -48
  309. package/lib/release_candidate.js +0 -50
  310. package/lib/release_center.js +0 -1093
  311. package/lib/release_history.js +0 -205
  312. package/lib/release_manifest.js +0 -113
  313. package/lib/release_pack.js +0 -453
  314. package/lib/request_rate_limit.js +0 -144
  315. package/lib/resource_views.js +0 -31
  316. package/lib/revenue_launch_path_contract.js +0 -326
  317. package/lib/route_matchers.js +0 -34
  318. package/lib/route_params.js +0 -19
  319. package/lib/runtime_bridge.js +0 -124
  320. package/lib/runtime_operations_worker.js +0 -282
  321. package/lib/runtime_state_store.js +0 -495
  322. package/lib/settlement_bsv_live.js +0 -600
  323. package/lib/settlement_bsv_teranode.js +0 -117
  324. package/lib/settlement_lane_contract.js +0 -111
  325. package/lib/shared_signals_lane_contract.js +0 -54
  326. package/lib/signed_audit_provenance_contract.js +0 -83
  327. package/lib/soft_launch_pack.js +0 -78
  328. package/lib/stablecoin_lane_contract.js +0 -76
  329. package/lib/stripe_mpp_lane_contract.js +0 -96
  330. package/lib/stripe_mpp_payment_front.js +0 -258
  331. package/lib/telemetry_lane_contract.js +0 -54
  332. package/lib/treasury_destinations_contract.js +0 -764
  333. package/lib/treasury_egress_policy_contract.js +0 -91
  334. package/lib/treasury_lane_contract.js +0 -84
  335. package/lib/trust_portability_boundary_contract.js +0 -72
  336. package/lib/x402_facilitator.js +0 -198
  337. package/lib/x402_lane_contract.js +0 -136
  338. package/lib/x402_settlement_recovery.js +0 -175
  339. package/scripts/buyer_events_daily_report.js +0 -79
  340. package/scripts/registry_cli.js +0 -275
  341. package/scripts/state_backup_offsite.js +0 -196
  342. package/scripts/verify_a2a_rpc.js +0 -131
  343. package/scripts/verify_adapters.js +0 -554
  344. package/scripts/verify_agent_identity.js +0 -153
  345. package/scripts/verify_all.js +0 -4963
  346. package/scripts/verify_bolt_adapter.js +0 -128
  347. package/scripts/verify_buyer_event_log.js +0 -128
  348. package/scripts/verify_buyer_safety.js +0 -141
  349. package/scripts/verify_cdp_facilitator.js +0 -110
  350. package/scripts/verify_checkout_processor.js +0 -109
  351. package/scripts/verify_commerce_bridge.js +0 -177
  352. package/scripts/verify_control_plane_contracts.js +0 -34
  353. package/scripts/verify_control_plane_routes.js +0 -70
  354. package/scripts/verify_delivery_summary.js +0 -112
  355. package/scripts/verify_examples.js +0 -19
  356. package/scripts/verify_execution_dedupe.js +0 -189
  357. package/scripts/verify_execution_surfacing.js +0 -76
  358. package/scripts/verify_external_execution_retry.js +0 -140
  359. package/scripts/verify_final_activation.js +0 -191
  360. package/scripts/verify_first_run_conversions.js +0 -81
  361. package/scripts/verify_framework_provider_promotion.js +0 -152
  362. package/scripts/verify_full_catalog.js +0 -174
  363. package/scripts/verify_integrations.js +0 -620
  364. package/scripts/verify_l402_adapter.js +0 -86
  365. package/scripts/verify_lifecycle_continuity.js +0 -96
  366. package/scripts/verify_lifecycle_spend_binding.js +0 -88
  367. package/scripts/verify_market_lane_depth.js +0 -94
  368. package/scripts/verify_market_lane_promotion.js +0 -103
  369. package/scripts/verify_market_lane_traces.js +0 -98
  370. package/scripts/verify_market_lanes.js +0 -102
  371. package/scripts/verify_mpp_adapter.js +0 -128
  372. package/scripts/verify_operator_observability_boundary.js +0 -83
  373. package/scripts/verify_pricing_experiment_plan.js +0 -124
  374. package/scripts/verify_production_readiness.js +0 -268
  375. package/scripts/verify_provider_network.js +0 -819
  376. package/scripts/verify_provider_runtime_breadth.js +0 -109
  377. package/scripts/verify_provider_runtime_journeys.js +0 -111
  378. package/scripts/verify_rate_limiter.js +0 -52
  379. package/scripts/verify_refund_credit.js +0 -106
  380. package/scripts/verify_revenue_launch_paths.js +0 -105
  381. package/scripts/verify_runtime_durability.js +0 -52
  382. package/scripts/verify_runtime_state_guard.js +0 -117
  383. package/scripts/verify_service.js +0 -15421
  384. package/scripts/verify_settlement.js +0 -116
  385. package/scripts/verify_tooling.js +0 -1412
  386. package/scripts/verify_treasury_public_boundary.js +0 -97
  387. package/scripts/verify_verify_execute_settle.js +0 -209
  388. package/scripts/verify_x402_facilitator.js +0 -307
  389. package/scripts/verify_x402_settlement_recovery.js +0 -153
  390. package/server.js +0 -12245
@@ -1,852 +0,0 @@
1
- "use strict";
2
-
3
- // Open execution-provider network (dynamic, state-backed) — the keystone that turns the
4
- // single-tenant env-pinned execution targets (external_execution_runtime.js) into a
5
- // two-sided network any provider can plug into: providers self-register their endpoint +
6
- // the capabilities they serve + their revenue-share; the runtime routes matching paid calls
7
- // to ACTIVE providers; settlement splits the money (provider cut vs platform take) and
8
- // records a per-provider ledger.
9
- //
10
- // Safe-by-default: a newly registered provider lands "pending" and is NOT routable until an
11
- // operator activates it, and a re-registration that changes the endpoint reverts an active
12
- // provider to pending (so a leaked registration bearer cannot silently redirect live paid
13
- // traffic). With an empty registry every function here is inert — zero behavior change.
14
-
15
- const crypto = require("crypto");
16
- const { clampTargetTimeoutMs, checkEndpointUrlShape } = require("./external_execution_policy");
17
-
18
- function normStr(value, fallback = "") {
19
- const s = typeof value === "string" ? value.trim() : "";
20
- return s || fallback;
21
- }
22
- function ensureObject(value) {
23
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
24
- }
25
- function normStrArray(value) {
26
- return Array.isArray(value) ? [...new Set(value.map((v) => normStr(v)).filter(Boolean))] : [];
27
- }
28
- function nowIso() {
29
- return new Date().toISOString();
30
- }
31
- function emptyRollup() {
32
- return { routed_calls: 0, settled_calls: 0, gross_minor: 0, provider_share_minor: 0, platform_take_minor: 0, paid_out_minor: 0 };
33
- }
34
-
35
- function emptyHealth() {
36
- return { status: "healthy", consecutive_failures: 0, last_success_at_iso: null, last_failure_at_iso: null, unhealthy_until_iso: null };
37
- }
38
-
39
- function emptyReputation() {
40
- return { success_count: 0, failure_count: 0 };
41
- }
42
-
43
- // Smoothed reputation score in (0,1] for ROUTING order. Uses an optimistic prior (+2 pseudo-
44
- // successes): a no-history provider scores 1.0 (OPTIMISTIC cold-start — it still gets a fair
45
- // first shot), and a single early transient failure scores ~0.67 (recoverable), NEVER 0.0 — so
46
- // a provider is not permanently doomed/starved by one bad call. Bad providers still sink over time.
47
- // Optional recency decay for the ROUTING score only (never money, never health exclusion). A
48
- // factor in (0,1) fades older observations on each update so recent performance dominates — a
49
- // provider that regressed sinks, one that recovered rises. Default 1.0 = NO decay, i.e. exactly
50
- // the lifetime-count behavior. Routing-only: raw lifetime counts stay intact for display.
51
- function reputationDecayFactor() {
52
- const n = Number(process.env.XYTARA_PROVIDER_REPUTATION_DECAY);
53
- return Number.isFinite(n) && n > 0 && n < 1 ? n : 1;
54
- }
55
-
56
- function reputationScore(rec) {
57
- const rep = (rec && rec.reputation) || emptyReputation();
58
- // Prefer the recency-weighted pseudo-counts when present; fall back to raw lifetime counts for a
59
- // no-history or pre-decay provider. With the default factor 1.0 the two are identical. Same
60
- // optimistic +2 prior either way (a new provider still scores 1.0 and gets a fair first shot).
61
- const success = Number.isFinite(Number(rep.decayed_success)) ? Number(rep.decayed_success) : (Number(rep.success_count) || 0);
62
- const failure = Number.isFinite(Number(rep.decayed_failure)) ? Number(rep.decayed_failure) : (Number(rep.failure_count) || 0);
63
- return (success + 2) / (success + failure + 2);
64
- }
65
-
66
- function reputationView(rec) {
67
- const rep = { ...emptyReputation(), ...((rec && rec.reputation) || {}) };
68
- const success = Number(rep.success_count) || 0;
69
- const failure = Number(rep.failure_count) || 0;
70
- const total = success + failure;
71
- return {
72
- success_count: success,
73
- failure_count: failure,
74
- total_calls: total,
75
- // RAW observed success rate for display; the smoothed reputationScore above is routing-only.
76
- success_rate: total === 0 ? null : Number((success / total).toFixed(4))
77
- };
78
- }
79
-
80
- function unhealthyThreshold() {
81
- const n = Number(process.env.XYTARA_PROVIDER_UNHEALTHY_THRESHOLD);
82
- return Number.isInteger(n) && n >= 1 ? n : 3;
83
- }
84
- function unhealthyCooldownMs() {
85
- const n = Number(process.env.XYTARA_PROVIDER_UNHEALTHY_COOLDOWN_MS);
86
- // Clamp to <= 24h so Date.now()+cooldown can never overflow a valid Date (which would throw
87
- // in .toISOString() and silently drop the unhealthy transition, leaving a bad provider live).
88
- return Number.isFinite(n) && n >= 0 ? Math.min(n, 86400000) : 60000;
89
- }
90
- function failoverMax() {
91
- const n = Number(process.env.XYTARA_PROVIDER_FAILOVER_MAX);
92
- return Number.isInteger(n) && n >= 1 ? n : 3;
93
- }
94
-
95
- // A provider is routable w.r.t. health when it is healthy, OR it was marked unhealthy but the
96
- // cooldown has elapsed (half-open: give it one more chance). Healthy is preferred over
97
- // half-open at selection time. Returns null for routable, or "unhealthy_cooldown" when skipped.
98
- function healthRoutingState(health, nowMs) {
99
- const h = health || emptyHealth();
100
- if (h.status !== "unhealthy") return { routable: true, half_open: false };
101
- const until = h.unhealthy_until_iso ? Date.parse(h.unhealthy_until_iso) : 0;
102
- // null -> 0 -> elapsed; a garbage/unparseable timestamp -> NaN -> treat as elapsed (recover,
103
- // never strand a provider permanently excluded). Only a valid FUTURE timestamp excludes it.
104
- if (!Number.isFinite(until) || nowMs >= until) return { routable: true, half_open: true };
105
- return { routable: false, half_open: false };
106
- }
107
-
108
- const PAYOUT_STATUSES = new Set(["pending", "settled", "cancelled"]);
109
-
110
- const PROVIDER_STATUSES = new Set(["pending", "active", "suspended"]);
111
-
112
- function timingSafeStrEqual(a, b) {
113
- const x = Buffer.from(String(a || ""), "utf8");
114
- const y = Buffer.from(String(b || ""), "utf8");
115
- if (x.length !== y.length || x.length === 0) return false;
116
- try { return crypto.timingSafeEqual(x, y); } catch (_) { return false; }
117
- }
118
-
119
- function maxRevenueShareBps() {
120
- const raw = Number(process.env.XYTARA_PROVIDER_MAX_REVENUE_SHARE_BPS);
121
- return Number.isInteger(raw) && raw >= 0 && raw <= 10000 ? raw : 10000;
122
- }
123
-
124
- // Fields whose change on an ACTIVE provider must force re-approval (back to pending): the
125
- // endpoint it is called at, the capabilities it serves (which calls route to it), its
126
- // revenue split, and the token used to call it. Metadata (label/pricing/timeout) is exempt.
127
- function routingMaterialChanged(existing, next) {
128
- // JSON-encode the sorted, deduped members (not a delimiter-join) so a member containing
129
- // the separator cannot collide two different sets onto the same string.
130
- const sameSet = (a, b) => {
131
- const norm = (arr) => JSON.stringify([...new Set((arr || []).map(String))].sort());
132
- return norm(a) === norm(b);
133
- };
134
- return existing.endpoint_url !== next.endpoint_url
135
- || !sameSet(existing.task_refs, next.task_refs)
136
- || !sameSet(existing.tool_names, next.tool_names)
137
- || !sameSet(existing.integration_ids, next.integration_ids)
138
- || existing.revenue_share_bps !== next.revenue_share_bps
139
- || (existing.auth_token || null) !== (next.auth_token || null);
140
- }
141
-
142
- function ensureProviderCollections(state) {
143
- if (!state || typeof state !== "object") return;
144
- if (!(state.providerRegistry instanceof Map)) state.providerRegistry = new Map();
145
- if (!Array.isArray(state.providerSettlementLedger)) state.providerSettlementLedger = [];
146
- if (!Array.isArray(state.providerPayouts)) state.providerPayouts = [];
147
- }
148
-
149
- function rollupField(rec, field) {
150
- return rec && rec.rollup && Number.isFinite(Number(rec.rollup[field])) ? Number(rec.rollup[field]) : 0;
151
- }
152
-
153
- // Ledger rows and payouts belong to a REGISTRATION of a provider_id, not to the id itself: a
154
- // removed id that someone else re-registers must never see, inherit, or be credited by the
155
- // previous owner's rows. Legacy rows/records without an epoch match each other (null === null).
156
- function sameRegistrationEpoch(row, rec) {
157
- return ((row && row.registration_epoch) || null) === ((rec && rec.registration_epoch) || null);
158
- }
159
-
160
- // A payout mid-dispatch to the signer must not be cancellable: the signer may already have sent
161
- // the funds, and freeing the reservation would let a second payout re-send them. The guard
162
- // expires (process died during dispatch) so an operator can still reconcile a stuck record.
163
- const IN_FLIGHT_CANCEL_GUARD_MS = 5 * 60 * 1000;
164
- function payoutInFlight(payout) {
165
- if (!payout || payout.dispatch_state !== "in_flight") return false;
166
- const started = Date.parse(payout.dispatch_started_at_iso || "");
167
- return !Number.isFinite(started) || Date.now() - started < IN_FLIGHT_CANCEL_GUARD_MS;
168
- }
169
-
170
- // What a provider is owed, accounting for already-settled payouts AND outstanding pending ones
171
- // so two concurrent pending payouts can never over-draw the accrued share.
172
- // accrued = cumulative provider_share credited by settlements
173
- // paid_out = cumulative confirmed (settled) payouts
174
- // pending = sum of not-yet-confirmed payouts
175
- // available = accrued - paid_out - pending (the max a NEW payout may claim)
176
- // owed = accrued - paid_out (total still outstanding, incl. pending)
177
- function computeProviderPayoutState(state, providerId) {
178
- ensureProviderCollections(state);
179
- const rec = state.providerRegistry.get(normStr(providerId));
180
- if (!rec) return null;
181
- const accrued = rollupField(rec, "provider_share_minor");
182
- const paidOut = rollupField(rec, "paid_out_minor");
183
- const pending = state.providerPayouts
184
- .filter((p) => p.provider_id === rec.provider_id && sameRegistrationEpoch(p, rec) && p.status === "pending")
185
- .reduce((sum, p) => sum + (Number(p.amount_minor) || 0), 0);
186
- return {
187
- provider_id: rec.provider_id,
188
- payout_address: rec.payout_address || null,
189
- payout_network: rec.payout_network || null,
190
- payout_asset: rec.payout_asset || null,
191
- currency: "USD_CENTS",
192
- accrued_minor: accrued,
193
- paid_out_minor: paidOut,
194
- pending_minor: pending,
195
- available_minor: Math.max(0, accrued - paidOut - pending),
196
- owed_minor: Math.max(0, accrued - paidOut)
197
- };
198
- }
199
-
200
- // Never leaks auth_token — callers that need to CALL the provider read the stored record
201
- // directly (resolveRegisteredTarget); everything buyer/operator-facing uses this view.
202
- function buildProviderPublicView(rec) {
203
- if (!rec) return null;
204
- return {
205
- provider_id: rec.provider_id,
206
- label: rec.label,
207
- endpoint_url: rec.endpoint_url,
208
- task_refs: rec.task_refs,
209
- tool_names: rec.tool_names,
210
- integration_ids: rec.integration_ids,
211
- revenue_share_bps: rec.revenue_share_bps,
212
- pricing: rec.pricing,
213
- status: rec.status,
214
- timeout_ms: rec.timeout_ms,
215
- auth_bound: Boolean(rec.auth_token),
216
- payout_bound: Boolean(rec.payout_address),
217
- payout_network: rec.payout_network || null,
218
- payout_asset: rec.payout_asset || null,
219
- registered_at_iso: rec.registered_at_iso,
220
- activated_at_iso: rec.activated_at_iso,
221
- updated_at_iso: rec.updated_at_iso,
222
- rollup: { ...emptyRollup(), ...(rec.rollup || {}) },
223
- health: { ...emptyHealth(), ...(rec.health || {}) },
224
- reputation: reputationView(rec)
225
- };
226
- }
227
-
228
- // POST register (or idempotent update keyed on provider_id).
229
- function registerProvider(state, input) {
230
- ensureProviderCollections(state);
231
- const b = ensureObject(input);
232
- const endpointUrl = normStr(b.endpoint_url);
233
- if (!endpointUrl || !/^https?:\/\//i.test(endpointUrl)) {
234
- return { ok: false, reason: "endpoint_url_required" };
235
- }
236
- // Endpoint policy (strict on hosted deploys): https only, no loopback/private/link-local/metadata
237
- // hosts, no embedded credentials — the runtime POSTs to this URL, so it must not be usable as a
238
- // proxy into the runtime's own network. Re-checked (with DNS) at dispatch time.
239
- const endpointPolicy = checkEndpointUrlShape(endpointUrl);
240
- if (!endpointPolicy.ok) {
241
- return { ok: false, reason: endpointPolicy.reason, detail: endpointPolicy.detail || null, endpoint_policy: endpointPolicy.policy };
242
- }
243
- const taskRefs = normStrArray(b.task_refs);
244
- const toolNames = normStrArray(b.tool_names);
245
- const integrationIds = normStrArray(b.integration_ids);
246
- if (taskRefs.length + toolNames.length + integrationIds.length < 1) {
247
- return { ok: false, reason: "capability_selector_required" };
248
- }
249
- const shareBps = Number(b.revenue_share_bps);
250
- if (!Number.isInteger(shareBps) || shareBps < 0 || shareBps > 10000) {
251
- return { ok: false, reason: "revenue_share_bps_invalid" };
252
- }
253
- if (shareBps > maxRevenueShareBps()) {
254
- return { ok: false, reason: "revenue_share_bps_exceeds_max", max_revenue_share_bps: maxRevenueShareBps() };
255
- }
256
- const providerId = normStr(b.provider_id) || `provider_${crypto.randomBytes(6).toString("hex")}`;
257
- const existing = state.providerRegistry.get(providerId) || null;
258
-
259
- // Per-provider ownership: a new provider_id mints an owner_secret (returned ONCE); any
260
- // re-registration of an existing provider_id MUST present that secret. This stops one
261
- // holder of the shared registration bearer from mutating, re-pointing, or DoS-ing another
262
- // provider's record.
263
- if (existing && !timingSafeStrEqual(normStr(b.owner_secret), existing.owner_secret)) {
264
- return { ok: false, reason: "provider_owner_mismatch" };
265
- }
266
- const ownerSecret = existing ? existing.owner_secret : `prvown_${crypto.randomBytes(24).toString("hex")}`;
267
- const authToken = normStr(b.auth_token) || (existing && existing.auth_token) || null;
268
-
269
- // Payout destination (where this provider's revenue-share is paid). Owner-protected like
270
- // the rest of the record; NOT routing-material (changing it does not affect execution, so
271
- // it does not revert an active provider to pending). An eip155 address is shape-checked.
272
- const payoutNetwork = normStr(b.payout_network) || (existing && existing.payout_network) || "eip155:8453";
273
- const payoutAsset = normStr(b.payout_asset) || (existing && existing.payout_asset) || "USDC";
274
- const payoutAddress = normStr(b.payout_address) || (existing && existing.payout_address) || null;
275
- if (payoutAddress && /^eip155/i.test(payoutNetwork) && !/^0x[0-9a-fA-F]{40}$/.test(payoutAddress)) {
276
- return { ok: false, reason: "payout_address_invalid" };
277
- }
278
-
279
- const nextRouting = {
280
- endpoint_url: endpointUrl,
281
- task_refs: taskRefs,
282
- tool_names: toolNames,
283
- integration_ids: integrationIds,
284
- revenue_share_bps: shareBps,
285
- auth_token: authToken
286
- };
287
- // New -> pending. Re-registration that changes ANY routing-material field (endpoint, served
288
- // capabilities, revenue split, or call token) reverts an ACTIVE provider to pending, so an
289
- // expanded or altered capability can never receive paid traffic without operator re-approval.
290
- let status = existing ? existing.status : "pending";
291
- if (existing && existing.status === "active" && routingMaterialChanged(existing, nextRouting)) {
292
- status = "pending";
293
- }
294
-
295
- const record = {
296
- provider_id: providerId,
297
- label: normStr(b.label, providerId),
298
- endpoint_url: endpointUrl,
299
- task_refs: taskRefs,
300
- tool_names: toolNames,
301
- integration_ids: integrationIds,
302
- revenue_share_bps: shareBps,
303
- pricing: ensureObject(b.pricing),
304
- payout_address: payoutAddress,
305
- payout_network: payoutNetwork,
306
- payout_asset: payoutAsset,
307
- auth_token: authToken,
308
- owner_secret: ownerSecret,
309
- // Minted once per registration; every ledger row / payout is stamped with it (see
310
- // sameRegistrationEpoch). A re-registration of an existing record keeps its epoch.
311
- registration_epoch: existing ? (existing.registration_epoch || null) : `reg_${crypto.randomBytes(6).toString("hex")}`,
312
- // Capped (30s): a longer call could not settle inside the x402 authorization window.
313
- timeout_ms: clampTargetTimeoutMs(b.timeout_ms),
314
- status,
315
- registered_by: normStr(b.registered_by) || (existing && existing.registered_by) || null,
316
- registered_at_iso: existing ? existing.registered_at_iso : nowIso(),
317
- activated_at_iso: status === "active" ? (existing && existing.activated_at_iso) || null : (existing && existing.activated_at_iso) || null,
318
- updated_at_iso: nowIso(),
319
- rollup: existing ? existing.rollup : emptyRollup(),
320
- health: existing ? (existing.health || emptyHealth()) : emptyHealth(),
321
- reputation: existing ? (existing.reputation || emptyReputation()) : emptyReputation()
322
- };
323
- state.providerRegistry.set(providerId, record);
324
- const result = { ok: true, provider_id: providerId, status, provider: buildProviderPublicView(record) };
325
- if (!existing) result.owner_secret = ownerSecret; // returned exactly once, at first registration
326
- return result;
327
- }
328
-
329
- // Operator activate / suspend / re-pend.
330
- function setProviderStatus(state, providerId, status) {
331
- ensureProviderCollections(state);
332
- const nextStatus = normStr(status);
333
- if (!PROVIDER_STATUSES.has(nextStatus)) return { ok: false, reason: "invalid_status" };
334
- const rec = state.providerRegistry.get(normStr(providerId));
335
- if (!rec) return { ok: false, reason: "provider_not_found" };
336
- const next = {
337
- ...rec,
338
- status: nextStatus,
339
- activated_at_iso: nextStatus === "active" ? rec.activated_at_iso || nowIso() : rec.activated_at_iso,
340
- updated_at_iso: nowIso()
341
- };
342
- state.providerRegistry.set(rec.provider_id, next);
343
- return { ok: true, provider: buildProviderPublicView(next) };
344
- }
345
-
346
- function getProvider(state, providerId) {
347
- ensureProviderCollections(state);
348
- return state.providerRegistry.get(normStr(providerId)) || null;
349
- }
350
-
351
- // Operator removes a provider from the registry (e.g. to purge suspended/spam records and free
352
- // the pending pool). Refuses when the provider still has an unpaid owed balance unless `force`,
353
- // so a provider with earnings can't be silently dropped.
354
- function removeProvider(state, providerId, options = {}) {
355
- ensureProviderCollections(state);
356
- const id = normStr(providerId);
357
- const rec = state.providerRegistry.get(id);
358
- if (!rec) return { ok: false, reason: "provider_not_found" };
359
- const owed = Math.max(0, rollupField(rec, "provider_share_minor") - rollupField(rec, "paid_out_minor"));
360
- if (owed > 0 && !(options && options.force)) {
361
- return { ok: false, reason: "provider_has_unpaid_balance", owed_minor: owed };
362
- }
363
- // Cancel this registration's pending payouts so they can never be confirmed against (or reserve
364
- // balance of) a later registration of the same id. A payout mid-dispatch may already have been
365
- // sent, so it is left pending for manual reconciliation. Ledger rows are retained as the
366
- // operator's audit trail; they are epoch-scoped so a new registration never sees them.
367
- const cancelledPendingPayouts = [];
368
- const inFlightPayouts = [];
369
- for (const payout of state.providerPayouts) {
370
- if (payout.provider_id !== id || payout.status !== "pending" || !sameRegistrationEpoch(payout, rec)) continue;
371
- if (payoutInFlight(payout)) {
372
- inFlightPayouts.push(payout.payout_id);
373
- continue;
374
- }
375
- payout.status = "cancelled";
376
- payout.cancelled_at_iso = nowIso();
377
- payout.note = [payout.note, "provider_removed"].filter(Boolean).join("; ");
378
- cancelledPendingPayouts.push(payout.payout_id);
379
- }
380
- state.providerRegistry.delete(id);
381
- return {
382
- ok: true,
383
- removed: id,
384
- forced: Boolean(options && options.force),
385
- owed_minor_at_removal: owed,
386
- cancelled_pending_payouts: cancelledPendingPayouts,
387
- in_flight_payouts_left_pending: inFlightPayouts
388
- };
389
- }
390
-
391
- function listProviders(state, filters = {}) {
392
- ensureProviderCollections(state);
393
- let items = Array.from(state.providerRegistry.values());
394
- const status = normStr(filters && filters.status);
395
- if (status) items = items.filter((p) => p.status === status);
396
- return items;
397
- }
398
-
399
- // Resolve an ACTIVE registered provider for a call. Mirrors the env-target selectors
400
- // (task_ref / tool_name / integration_id) but a provider matches if the call hits ANY of
401
- // the capabilities it declared it serves. Returns a target shaped for executeExternalTarget.
402
- function providerMatchesCall(p, taskRefs, toolName, integrationIds) {
403
- if (p.status !== "active") return false;
404
- if (p.task_refs.length && p.task_refs.some((r) => taskRefs.includes(r))) return true;
405
- if (p.tool_names.length && toolName && p.tool_names.includes(toolName)) return true;
406
- if (p.integration_ids.length && p.integration_ids.some((id) => integrationIds.has(id))) return true;
407
- return false;
408
- }
409
-
410
- // Hosted providers can be cold (a free-tier host takes 20–50 s to wake). On a hosted deploy a
411
- // registered provider gets at least this long per attempt, inside the shared execution budget, so
412
- // a waking provider isn't cut off at its 10 s default and reported as a buyer-input failure.
413
- function providerMinTimeoutMs() {
414
- const raw = process.env.XYTARA_PROVIDER_MIN_TIMEOUT_MS;
415
- const fallback = process.env.RENDER ? 25000 : 0;
416
- const n = raw === undefined || raw === "" ? fallback : Number(raw);
417
- return Number.isFinite(n) && n > 0 ? Math.min(30000, Math.round(n)) : 0;
418
- }
419
-
420
- function targetFromProvider(p) {
421
- return {
422
- target_id: p.provider_id,
423
- endpoint_url: p.endpoint_url,
424
- task_ref: null,
425
- tool_name: null,
426
- integration_id: null,
427
- provider: p.provider_id,
428
- auth_token: p.auth_token,
429
- timeout_ms: Math.max(Number(p.timeout_ms) || 0, providerMinTimeoutMs()),
430
- enabled: true,
431
- registered_provider_id: p.provider_id
432
- };
433
- }
434
-
435
- // All ACTIVE + health-routable providers matching the call, ordered healthy-first then
436
- // half-open (recovered past their cooldown), capped at the failover max — so a caller can try
437
- // them in sequence and fail over when one is down. Currently-unhealthy (in-cooldown) providers
438
- // are excluded.
439
- function extractCallSelectors(payload, quote) {
440
- const body = ensureObject(payload && payload.body);
441
- const taskRefs = Array.isArray(payload && payload.tasks) && payload.tasks.length > 0
442
- ? payload.tasks.map((t) => normStr(t && t.task_ref)).filter(Boolean)
443
- : [normStr(payload && payload.task_ref)].filter(Boolean);
444
- const toolName = normStr(body.tool_name) || null;
445
- const integrationIds = new Set(
446
- []
447
- .concat(normStr(payload && payload.integration_id) ? [normStr(payload && payload.integration_id)] : [])
448
- .concat(Array.isArray(payload && payload.integration_ids) ? payload.integration_ids.map((v) => normStr(v)).filter(Boolean) : [])
449
- .concat(Array.isArray(quote && quote.integration_ids) ? quote.integration_ids.map((v) => normStr(v)).filter(Boolean) : [])
450
- );
451
- return { taskRefs, toolName, integrationIds };
452
- }
453
-
454
- // Whether ANY active provider matches the call, regardless of health. Lets the caller tell
455
- // "no provider serves this capability" (fall through to local handling) apart from "providers
456
- // serve it but are all currently unhealthy" (fail — don't charge for undelivered work).
457
- function hasActiveMatchingProvider(state, payload, quote) {
458
- ensureProviderCollections(state);
459
- if (state.providerRegistry.size < 1) return false;
460
- const { taskRefs, toolName, integrationIds } = extractCallSelectors(payload, quote);
461
- for (const p of state.providerRegistry.values()) {
462
- if (providerMatchesCall(p, taskRefs, toolName, integrationIds)) return true;
463
- }
464
- return false;
465
- }
466
-
467
- function resolveRegisteredTargets(state, payload, quote) {
468
- ensureProviderCollections(state);
469
- if (state.providerRegistry.size < 1) return [];
470
- const { taskRefs, toolName, integrationIds } = extractCallSelectors(payload, quote);
471
- const nowMs = Date.now();
472
- const healthy = [];
473
- const halfOpen = [];
474
- for (const p of state.providerRegistry.values()) {
475
- if (!providerMatchesCall(p, taskRefs, toolName, integrationIds)) continue;
476
- const hs = healthRoutingState(p.health, nowMs);
477
- if (!hs.routable) continue;
478
- (hs.half_open ? halfOpen : healthy).push(p);
479
- }
480
- // Within the healthy tier, prefer higher lifetime reputation (stable sort keeps registration
481
- // order for ties; a no-history provider scores 1.0 optimistically so it still gets routed and
482
- // can build a record). Half-open (recovering) providers always come after healthy ones.
483
- healthy.sort((a, b) => reputationScore(b) - reputationScore(a));
484
- return healthy.concat(halfOpen).map(targetFromProvider).slice(0, failoverMax());
485
- }
486
-
487
- function resolveRegisteredTarget(state, payload, quote) {
488
- const targets = resolveRegisteredTargets(state, payload, quote);
489
- return targets.length > 0 ? targets[0] : null;
490
- }
491
-
492
- // Update a provider's health after a routed call. Success resets to healthy; failure increments
493
- // consecutive_failures and, on crossing the threshold, marks it unhealthy with a cooldown
494
- // (excluded from routing until the cooldown elapses, then half-open for one retry).
495
- function recordProviderHealth(state, providerId, result = {}) {
496
- ensureProviderCollections(state);
497
- const rec = state.providerRegistry.get(normStr(providerId));
498
- if (!rec) return { ok: false, reason: "provider_not_found" };
499
- const h = rec.health || emptyHealth();
500
- const stamp = nowIso();
501
- let next;
502
- if (result && result.ok) {
503
- next = { status: "healthy", consecutive_failures: 0, last_success_at_iso: stamp, last_failure_at_iso: h.last_failure_at_iso || null, unhealthy_until_iso: null };
504
- } else {
505
- const failures = (Number(h.consecutive_failures) || 0) + 1;
506
- const nowUnhealthy = failures >= unhealthyThreshold();
507
- next = {
508
- status: nowUnhealthy ? "unhealthy" : h.status,
509
- consecutive_failures: failures,
510
- last_success_at_iso: h.last_success_at_iso || null,
511
- last_failure_at_iso: stamp,
512
- unhealthy_until_iso: nowUnhealthy ? new Date(Date.now() + unhealthyCooldownMs()).toISOString() : (h.unhealthy_until_iso || null)
513
- };
514
- }
515
- const rep = rec.reputation || emptyReputation();
516
- const ok = Boolean(result && result.ok);
517
- // Raw lifetime counts (for display / success_rate) — unchanged integer-increment semantics.
518
- const rawSuccess = (Number(rep.success_count) || 0) + (ok ? 1 : 0);
519
- const rawFailure = (Number(rep.failure_count) || 0) + (ok ? 0 : 1);
520
- // Recency-weighted pseudo-counts (routing order). Seeded from the raw counts the first time, then
521
- // decayed each update. With the default factor 1.0 these stay equal to the raw counts exactly.
522
- const decay = reputationDecayFactor();
523
- const baseSuccess = Number.isFinite(Number(rep.decayed_success)) ? Number(rep.decayed_success) : (Number(rep.success_count) || 0);
524
- const baseFailure = Number.isFinite(Number(rep.decayed_failure)) ? Number(rep.decayed_failure) : (Number(rep.failure_count) || 0);
525
- const nextRep = {
526
- success_count: rawSuccess,
527
- failure_count: rawFailure,
528
- decayed_success: baseSuccess * decay + (ok ? 1 : 0),
529
- decayed_failure: baseFailure * decay + (ok ? 0 : 1)
530
- };
531
- state.providerRegistry.set(rec.provider_id, { ...rec, health: next, reputation: nextRep, updated_at_iso: stamp });
532
- return { ok: true, health: next, reputation: nextRep };
533
- }
534
-
535
- // Count a routed dispatch (whether or not it later settles).
536
- function recordProviderRouting(state, providerId) {
537
- ensureProviderCollections(state);
538
- const rec = state.providerRegistry.get(normStr(providerId));
539
- if (!rec) return { ok: false, reason: "provider_not_found" };
540
- const roll = rec.rollup || emptyRollup();
541
- state.providerRegistry.set(rec.provider_id, {
542
- ...rec,
543
- rollup: { ...roll, routed_calls: (roll.routed_calls || 0) + 1 },
544
- updated_at_iso: nowIso()
545
- });
546
- return { ok: true };
547
- }
548
-
549
- // Split a settled amount for a provider and record a ledger entry. provider_share is floored
550
- // (platform keeps the rounding remainder); amounts are integer minor units.
551
- function recordProviderSettlement(state, input) {
552
- ensureProviderCollections(state);
553
- const b = ensureObject(input);
554
- const rec = state.providerRegistry.get(normStr(b.provider_id));
555
- if (!rec) return { ok: false, reason: "provider_not_found" };
556
- const gross = Math.max(0, Math.round(Number(b.amount_minor) || 0));
557
- const providerShare = Math.floor((gross * rec.revenue_share_bps) / 10000);
558
- const platformTake = gross - providerShare;
559
- const optionalMinor = (value) => (value !== undefined && value !== null && Number.isFinite(Number(value)) ? Math.round(Number(value)) : null);
560
- const entry = {
561
- settlement_entry_id: `provset_${crypto.randomBytes(6).toString("hex")}`,
562
- provider_id: rec.provider_id,
563
- registration_epoch: rec.registration_epoch || null,
564
- transaction_id: normStr(b.transaction_id) || null,
565
- settlement_ref: normStr(b.settlement_ref) || null,
566
- task_ref: normStr(b.task_ref) || null,
567
- currency: normStr(b.currency) || null,
568
- // What the accrual was based on (the caller reconciles quote vs. what actually settled).
569
- settlement_basis: normStr(b.settlement_basis) || "quote",
570
- quoted_minor: optionalMinor(b.quoted_minor),
571
- settled_minor: optionalMinor(b.settled_minor),
572
- reconciled: b.reconciled === undefined ? true : Boolean(b.reconciled),
573
- gross_minor: gross,
574
- revenue_share_bps: rec.revenue_share_bps,
575
- provider_share_minor: providerShare,
576
- platform_take_minor: platformTake,
577
- recorded_at_iso: nowIso()
578
- };
579
- state.providerSettlementLedger.push(entry);
580
- const roll = rec.rollup || emptyRollup();
581
- state.providerRegistry.set(rec.provider_id, {
582
- ...rec,
583
- rollup: {
584
- ...roll,
585
- settled_calls: (roll.settled_calls || 0) + 1,
586
- gross_minor: (roll.gross_minor || 0) + gross,
587
- provider_share_minor: (roll.provider_share_minor || 0) + providerShare,
588
- platform_take_minor: (roll.platform_take_minor || 0) + platformTake
589
- },
590
- updated_at_iso: nowIso()
591
- });
592
- return { ok: true, settlement: entry };
593
- }
594
-
595
- // Optional `registration_epoch` filter (pass null to select legacy un-stamped rows) scopes the
596
- // rows to ONE registration of the id; without it the operator sees the full audit trail.
597
- function hasEpochFilter(filters) {
598
- return Boolean(filters) && Object.prototype.hasOwnProperty.call(filters, "registration_epoch");
599
- }
600
-
601
- function listProviderSettlements(state, filters = {}) {
602
- ensureProviderCollections(state);
603
- let items = state.providerSettlementLedger.slice();
604
- const providerId = normStr(filters && filters.provider_id);
605
- if (providerId) items = items.filter((e) => e.provider_id === providerId);
606
- if (hasEpochFilter(filters)) items = items.filter((e) => sameRegistrationEpoch(e, { registration_epoch: filters.registration_epoch }));
607
- return items;
608
- }
609
-
610
- // Create a payout CLAIM for a provider (operator action). Amount defaults to the full
611
- // available balance and can NEVER exceed available (accrued - paid - pending), so concurrent
612
- // pending payouts cannot over-draw. The runtime does NOT move funds here — it records the
613
- // intent; the operator executes the on-chain transfer and confirms it with the tx hash.
614
- function createProviderPayout(state, providerId, input = {}) {
615
- ensureProviderCollections(state);
616
- const rec = state.providerRegistry.get(normStr(providerId));
617
- if (!rec) return { ok: false, reason: "provider_not_found" };
618
- if (!rec.payout_address) return { ok: false, reason: "payout_address_not_set" };
619
- const payoutState = computeProviderPayoutState(state, rec.provider_id);
620
- const available = payoutState.available_minor;
621
- if (available <= 0) return { ok: false, reason: "nothing_payable", available_minor: available };
622
- const b = ensureObject(input);
623
- const requested = b.amount_minor !== undefined && b.amount_minor !== null ? Math.round(Number(b.amount_minor)) : available;
624
- if (!Number.isFinite(requested) || requested <= 0) return { ok: false, reason: "amount_invalid" };
625
- if (requested > available) return { ok: false, reason: "amount_exceeds_available", available_minor: available };
626
- const payout = {
627
- payout_id: `provpay_${crypto.randomBytes(6).toString("hex")}`,
628
- provider_id: rec.provider_id,
629
- registration_epoch: rec.registration_epoch || null,
630
- dispatch_state: null,
631
- dispatch_started_at_iso: null,
632
- payout_address: rec.payout_address,
633
- payout_network: rec.payout_network || null,
634
- payout_asset: rec.payout_asset || null,
635
- amount_minor: requested,
636
- currency: "USD_CENTS",
637
- status: "pending",
638
- tx_ref: null,
639
- created_at_iso: nowIso(),
640
- settled_at_iso: null,
641
- cancelled_at_iso: null,
642
- note: normStr(b.note) || null
643
- };
644
- state.providerPayouts.push(payout);
645
- return { ok: true, payout, payout_state: computeProviderPayoutState(state, rec.provider_id) };
646
- }
647
-
648
- function getProviderPayout(state, payoutId) {
649
- ensureProviderCollections(state);
650
- return state.providerPayouts.find((p) => p.payout_id === normStr(payoutId)) || null;
651
- }
652
-
653
- // Confirm a payout the operator executed on-chain. Idempotent: re-confirming a settled payout
654
- // returns it without double-crediting paid_out. Only a pending payout credits paid_out_minor.
655
- function confirmProviderPayout(state, payoutId, input = {}) {
656
- ensureProviderCollections(state);
657
- const payout = getProviderPayout(state, payoutId);
658
- if (!payout) return { ok: false, reason: "payout_not_found" };
659
- if (payout.status === "settled") return { ok: true, payout, idempotent: true };
660
- if (payout.status !== "pending") return { ok: false, reason: "payout_not_pending", status: payout.status };
661
- const txRef = normStr(ensureObject(input).tx_ref);
662
- if (!txRef) return { ok: false, reason: "tx_ref_required" };
663
- // A tx_ref may back only ONE settled payout. Otherwise two payouts credit paid_out for a
664
- // single on-chain transfer, silently under-paying the provider and burning reserved balance.
665
- if (state.providerPayouts.some((p) => p.status === "settled" && p.tx_ref === txRef && p.payout_id !== payout.payout_id)) {
666
- return { ok: false, reason: "duplicate_tx_ref" };
667
- }
668
- payout.status = "settled";
669
- payout.tx_ref = txRef;
670
- payout.settled_at_iso = nowIso();
671
- payout.dispatch_state = null;
672
- const rec = state.providerRegistry.get(payout.provider_id);
673
- // Credit paid_out ONLY on the registration the payout belongs to — a payout from a previous
674
- // (removed) registration of the same id is recorded but never credits the current record.
675
- const rollupCredited = Boolean(rec && sameRegistrationEpoch(payout, rec));
676
- if (rollupCredited) {
677
- const roll = rec.rollup || emptyRollup();
678
- state.providerRegistry.set(rec.provider_id, {
679
- ...rec,
680
- rollup: { ...roll, paid_out_minor: (Number(roll.paid_out_minor) || 0) + payout.amount_minor },
681
- updated_at_iso: nowIso()
682
- });
683
- }
684
- return { ok: true, payout, rollup_credited: rollupCredited, payout_state: rec ? computeProviderPayoutState(state, payout.provider_id) : null };
685
- }
686
-
687
- // Cancel a pending payout — frees its reserved amount back into available. Never a settled one.
688
- function cancelProviderPayout(state, payoutId) {
689
- ensureProviderCollections(state);
690
- const payout = getProviderPayout(state, payoutId);
691
- if (!payout) return { ok: false, reason: "payout_not_found" };
692
- if (payout.status === "cancelled") return { ok: true, payout, idempotent: true };
693
- if (payout.status !== "pending") return { ok: false, reason: "payout_not_pending", status: payout.status };
694
- if (payoutInFlight(payout)) {
695
- return {
696
- ok: false,
697
- reason: "payout_in_flight",
698
- status: payout.status,
699
- dispatch_started_at_iso: payout.dispatch_started_at_iso || null,
700
- remediation: "this payout is being sent by the signer right now; wait for the dispatch result (it confirms or stays pending for reconciliation) instead of cancelling"
701
- };
702
- }
703
- payout.status = "cancelled";
704
- payout.cancelled_at_iso = nowIso();
705
- return {
706
- ok: true,
707
- payout,
708
- warning: "cancel only a payout NOT yet sent on-chain — the amount returns to available and can be paid again"
709
- };
710
- }
711
-
712
- // Dispatch bookkeeping for the external-signer flow: "in_flight" while the signer call is
713
- // outstanding (blocks cancel, see payoutInFlight), null once the signer has answered.
714
- function markProviderPayoutDispatch(state, payoutId, dispatchState) {
715
- ensureProviderCollections(state);
716
- const payout = getProviderPayout(state, payoutId);
717
- if (!payout) return { ok: false, reason: "payout_not_found" };
718
- if (dispatchState === "in_flight") {
719
- payout.dispatch_state = "in_flight";
720
- payout.dispatch_started_at_iso = nowIso();
721
- } else {
722
- payout.dispatch_state = null;
723
- }
724
- return { ok: true, payout };
725
- }
726
-
727
- // Last-resort safety net: a payout that was cancelled while (or after) the signer actually sent
728
- // the funds is put back to PENDING with the reported tx and flagged, so the reservation is held
729
- // again and the operator reconciles (confirm with the tx) instead of the ledger re-paying it.
730
- function reinstateCancelledPayoutForReconciliation(state, payoutId, txRef) {
731
- ensureProviderCollections(state);
732
- const payout = getProviderPayout(state, payoutId);
733
- if (!payout) return { ok: false, reason: "payout_not_found" };
734
- if (payout.status !== "cancelled") return { ok: false, reason: "payout_not_cancelled", status: payout.status };
735
- payout.status = "pending";
736
- payout.cancelled_at_iso = null;
737
- payout.dispatch_state = null;
738
- payout.tx_ref = normStr(txRef) || payout.tx_ref || null;
739
- payout.reconcile_required = true;
740
- payout.note = [payout.note, "reinstated_after_cancel_during_dispatch"].filter(Boolean).join("; ");
741
- return { ok: true, payout };
742
- }
743
-
744
- function listProviderPayouts(state, filters = {}) {
745
- ensureProviderCollections(state);
746
- let items = state.providerPayouts.slice();
747
- const providerId = normStr(filters && filters.provider_id);
748
- const status = normStr(filters && filters.status);
749
- if (providerId) items = items.filter((p) => p.provider_id === providerId);
750
- if (status) items = items.filter((p) => p.status === status);
751
- if (hasEpochFilter(filters)) items = items.filter((p) => sameRegistrationEpoch(p, { registration_epoch: filters.registration_epoch }));
752
- return items;
753
- }
754
-
755
- // Authenticate a provider acting on its OWN record using the owner_secret minted at
756
- // registration (constant-time compare). Returns the record on match, or a reason code. This is
757
- // the provider-SIDE auth (self-service), distinct from the operator bearer: it lets a
758
- // self-registered provider read its own earnings without granting any operator capability.
759
- function authenticateProviderOwner(state, providerId, ownerSecret) {
760
- ensureProviderCollections(state);
761
- const rec = state.providerRegistry.get(normStr(providerId));
762
- if (!rec) return { ok: false, reason: "provider_not_found" };
763
- if (!timingSafeStrEqual(normStr(ownerSecret), rec.owner_secret)) {
764
- return { ok: false, reason: "provider_owner_mismatch" };
765
- }
766
- return { ok: true, provider: rec };
767
- }
768
-
769
- // Read-only self-service earnings view for a provider: its public profile, computed payout state
770
- // (accrued / paid / pending / available / owed), its settlement ledger rows, and its payout
771
- // history. Composes existing read helpers only — never mutates state, and never leaks auth_token
772
- // or owner_secret (public view + the provider's own ledger rows). Caller must authenticate first.
773
- function buildProviderEarningsView(state, providerId) {
774
- ensureProviderCollections(state);
775
- const rec = state.providerRegistry.get(normStr(providerId));
776
- if (!rec) return null;
777
- // Scoped to THIS registration's epoch: a re-registered id never reads a previous owner's rows.
778
- const epoch = rec.registration_epoch || null;
779
- return {
780
- provider: buildProviderPublicView(rec),
781
- payout_state: computeProviderPayoutState(state, rec.provider_id),
782
- settlements: listProviderSettlements(state, { provider_id: rec.provider_id, registration_epoch: epoch }),
783
- payouts: listProviderPayouts(state, { provider_id: rec.provider_id, registration_epoch: epoch })
784
- };
785
- }
786
-
787
- function summarizeProviderNetwork(state) {
788
- ensureProviderCollections(state);
789
- const providers = Array.from(state.providerRegistry.values());
790
- const byStatus = { pending: 0, active: 0, suspended: 0 };
791
- const totals = { gross_minor: 0, provider_share_minor: 0, platform_take_minor: 0, paid_out_minor: 0, owed_minor: 0, routed_calls: 0, settled_calls: 0 };
792
- for (const p of providers) {
793
- byStatus[p.status] = (byStatus[p.status] || 0) + 1;
794
- const share = rollupField(p, "provider_share_minor");
795
- const paid = rollupField(p, "paid_out_minor");
796
- totals.gross_minor += rollupField(p, "gross_minor");
797
- totals.provider_share_minor += share;
798
- totals.platform_take_minor += rollupField(p, "platform_take_minor");
799
- totals.paid_out_minor += paid;
800
- totals.owed_minor += Math.max(0, share - paid);
801
- totals.routed_calls += rollupField(p, "routed_calls");
802
- totals.settled_calls += rollupField(p, "settled_calls");
803
- }
804
- const nowMs = Date.now();
805
- const health = { routable: 0, unhealthy: 0 };
806
- for (const p of providers) {
807
- if (p.status !== "active") continue;
808
- if (healthRoutingState(p.health, nowMs).routable) health.routable += 1; else health.unhealthy += 1;
809
- }
810
- const pendingPayouts = state.providerPayouts.filter((p) => p.status === "pending");
811
- return {
812
- ok: true,
813
- provider_count: providers.length,
814
- by_status: byStatus,
815
- health,
816
- totals,
817
- settlement_ledger_entries: state.providerSettlementLedger.length,
818
- payouts: {
819
- total: state.providerPayouts.length,
820
- pending: pendingPayouts.length,
821
- pending_minor: pendingPayouts.reduce((sum, p) => sum + (Number(p.amount_minor) || 0), 0)
822
- }
823
- };
824
- }
825
-
826
- module.exports = {
827
- ensureProviderCollections,
828
- registerProvider,
829
- setProviderStatus,
830
- getProvider,
831
- removeProvider,
832
- listProviders,
833
- buildProviderPublicView,
834
- authenticateProviderOwner,
835
- buildProviderEarningsView,
836
- resolveRegisteredTarget,
837
- resolveRegisteredTargets,
838
- hasActiveMatchingProvider,
839
- recordProviderHealth,
840
- recordProviderRouting,
841
- recordProviderSettlement,
842
- listProviderSettlements,
843
- summarizeProviderNetwork,
844
- computeProviderPayoutState,
845
- createProviderPayout,
846
- getProviderPayout,
847
- confirmProviderPayout,
848
- cancelProviderPayout,
849
- markProviderPayoutDispatch,
850
- reinstateCancelledPayoutForReconciliation,
851
- listProviderPayouts
852
- };