settld 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (483) hide show
  1. package/README.md +93 -3
  2. package/SETTLD_VERSION +1 -1
  3. package/bin/settld-mcp +2 -0
  4. package/bin/settld.js +71 -0
  5. package/conformance/kernel-v0/README.md +7 -0
  6. package/conformance/kernel-v0/run.mjs +292 -4
  7. package/docs/ACCESS.md +57 -0
  8. package/docs/ADOPTION_CHECKLIST.md +44 -0
  9. package/docs/ALERTS.md +198 -0
  10. package/docs/ARCHITECTURE.md +69 -0
  11. package/docs/ARCHITECTURE_FOUNDER_GUIDE.md +284 -0
  12. package/docs/ARTIFACTS.md +60 -0
  13. package/docs/CERTIFICATION_CHECKLIST.md +33 -0
  14. package/docs/CIRCLE_SANDBOX_E2E.md +152 -0
  15. package/docs/CONFIG.md +297 -0
  16. package/docs/CONTRACTS_APIS.md +23 -0
  17. package/docs/DEPRECATION.md +31 -0
  18. package/docs/DOMAIN_MODEL.md +92 -0
  19. package/docs/EVENT_ENVELOPE.md +53 -0
  20. package/docs/FINANCE_PACK_FORMAT.md +53 -0
  21. package/docs/INCIDENT_TAXONOMY.md +30 -0
  22. package/docs/JOB_STATE_MACHINE.md +66 -0
  23. package/docs/KERNEL_COMPATIBLE.md +60 -0
  24. package/docs/KERNEL_V0.md +40 -0
  25. package/docs/KEY_ROTATION.md +80 -0
  26. package/docs/LEDGER.md +82 -0
  27. package/docs/LIVENESS.md +76 -0
  28. package/docs/MVP_BUILD_ORDER.md +36 -0
  29. package/docs/ONCALL_PLAYBOOK.md +39 -0
  30. package/docs/OPERATIONS_SIGNING.md +20 -0
  31. package/docs/OVERVIEW.md +190 -0
  32. package/docs/PERF_BASELINE.md +85 -0
  33. package/docs/PRD.md +77 -0
  34. package/docs/QUICKSTART_KERNEL_V0.md +96 -0
  35. package/docs/QUICKSTART_MCP.md +377 -0
  36. package/docs/QUICKSTART_MCP_HOSTS.md +210 -0
  37. package/docs/QUICKSTART_POLICY_PACKS.md +65 -0
  38. package/docs/QUICKSTART_PRODUCE.md +61 -0
  39. package/docs/QUICKSTART_PROFILES.md +198 -0
  40. package/docs/QUICKSTART_RELEASE_VERIFY.md +39 -0
  41. package/docs/QUICKSTART_SDK.md +125 -0
  42. package/docs/QUICKSTART_SDK_PYTHON.md +111 -0
  43. package/docs/QUICKSTART_VERIFY.md +54 -0
  44. package/docs/QUICKSTART_X402_GATEWAY.md +317 -0
  45. package/docs/README.md +33 -0
  46. package/docs/RELEASE_CHECKLIST.md +182 -0
  47. package/docs/RELEASING.md +82 -0
  48. package/docs/REPO_SETTINGS.md +37 -0
  49. package/docs/RUNBOOK.md +86 -0
  50. package/docs/SKILLS.md +42 -0
  51. package/docs/SKILL_BUNDLE_FORMAT.md +48 -0
  52. package/docs/SLO.md +131 -0
  53. package/docs/SUMMARY.md +17 -0
  54. package/docs/SUPPORT.md +31 -0
  55. package/docs/THREAT_MODEL.md +36 -0
  56. package/docs/TRUST.md +59 -0
  57. package/docs/WORKFLOW.md +35 -0
  58. package/docs/X402_BATCH_SETTLEMENT.md +126 -0
  59. package/docs/blog/2026-02-14-your-ai-agent-just-spent-500-where-is-the-receipt.md +73 -0
  60. package/docs/examples/x402-provider-payout-registry.example.json +14 -0
  61. package/docs/gitbook/README.md +64 -0
  62. package/docs/gitbook/SETUP.md +25 -0
  63. package/docs/gitbook/SUMMARY.md +15 -0
  64. package/docs/gitbook/api-reference.md +73 -0
  65. package/docs/gitbook/closepacks.md +55 -0
  66. package/docs/gitbook/conformance.md +59 -0
  67. package/docs/gitbook/core-primitives.md +85 -0
  68. package/docs/gitbook/dispute-lifecycle.md +33 -0
  69. package/docs/gitbook/faq.md +21 -0
  70. package/docs/gitbook/guides.md +49 -0
  71. package/docs/gitbook/operations-runbook.md +36 -0
  72. package/docs/gitbook/quickstart.md +103 -0
  73. package/docs/gitbook/replay-and-audit.md +30 -0
  74. package/docs/gitbook/sdk-reference.md +35 -0
  75. package/docs/gitbook/security-model.md +58 -0
  76. package/docs/integrations/README.md +15 -0
  77. package/docs/integrations/github-actions-verify.yml +31 -0
  78. package/docs/integrations/github-actions.md +34 -0
  79. package/docs/integrations/openclaw/CLAWHUB_PUBLISH_CHECKLIST.md +65 -0
  80. package/docs/integrations/openclaw/PUBLIC_QUICKSTART.md +95 -0
  81. package/docs/integrations/openclaw/settld-mcp-skill/SKILL.md +69 -0
  82. package/docs/integrations/openclaw/settld-mcp-skill/mcp-server.example.json +12 -0
  83. package/docs/kernel-compatible/capabilities.json +36 -0
  84. package/docs/marketing/agent-commerce-substrate.md +78 -0
  85. package/docs/marketing/hn-repost-2026-02-17.md +102 -0
  86. package/docs/marketing/show-hn-post.md +45 -0
  87. package/docs/ops/ARTIFACT_VERIFICATION_STATUS.md +43 -0
  88. package/docs/ops/BILLING_WEBHOOK_REPLAY.md +105 -0
  89. package/docs/ops/CI_FLAKE_BUDGET.md +31 -0
  90. package/docs/ops/DISPUTE_FINANCE_RECONCILIATION_PACKET.md +56 -0
  91. package/docs/ops/GO_LIVE_GATE_S13.md +27 -0
  92. package/docs/ops/HOSTED_BASELINE_R2.md +129 -0
  93. package/docs/ops/KERNEL_V0_SHIP_GATE.md +69 -0
  94. package/docs/ops/LIGHTHOUSE_PRODUCTION_CLOSE.md +51 -0
  95. package/docs/ops/MCP_COMPATIBILITY_MATRIX.md +30 -0
  96. package/docs/ops/MINIMUM_PRODUCTION_TOPOLOGY.md +89 -0
  97. package/docs/ops/P0_BACKEND_PROGRESS.md +150 -0
  98. package/docs/ops/PAYMENTS_ALPHA_R5.md +105 -0
  99. package/docs/ops/PILOT_ONBOARDING_RUNBOOK.md +112 -0
  100. package/docs/ops/PRODUCTION_DEPLOYMENT_CHECKLIST.md +140 -0
  101. package/docs/ops/R1_SLOS.md +66 -0
  102. package/docs/ops/RELEASE_SIGNING_INCIDENT.md +58 -0
  103. package/docs/ops/SELF_SERVE_LAUNCH_AUTOMATION.md +89 -0
  104. package/docs/ops/THROUGHPUT_DRILL_10X.md +48 -0
  105. package/docs/ops/TRUST_CONFIG_WIZARD.md +60 -0
  106. package/docs/ops/X402_PILOT_WEEKLY_METRICS.md +76 -0
  107. package/docs/ops/tool-call-disputes-holdback.md +52 -0
  108. package/docs/pilot-kit/PILOT_PACKAGE_SCORECARD_X402.md +46 -0
  109. package/docs/pilot-kit/README.md +29 -0
  110. package/docs/pilot-kit/architecture-one-pager.md +48 -0
  111. package/docs/pilot-kit/buyer-email.txt +19 -0
  112. package/docs/pilot-kit/buyer-one-pager.md +31 -0
  113. package/docs/pilot-kit/gtm-pilot-playbook.md +182 -0
  114. package/docs/pilot-kit/offline-verify.md +33 -0
  115. package/docs/pilot-kit/procurement-one-pager.md +50 -0
  116. package/docs/pilot-kit/rfp-clause.md +46 -0
  117. package/docs/pilot-kit/roi-calculator-template.csv +2 -0
  118. package/docs/pilot-kit/security-qa.md +153 -0
  119. package/docs/pilot-kit/security-summary.md +35 -0
  120. package/docs/plans/2026-02-13-mcp-spike-design.md +113 -0
  121. package/docs/plans/2026-02-20-trust-os-v1-jira-backlog.md +348 -0
  122. package/docs/plans/2026-02-21-agent-economic-actor-operating-model.md +169 -0
  123. package/docs/plans/2026-02-21-trust-os-v1-strategy.md +241 -0
  124. package/docs/research/2026-02-21-agent-spend-host-landscape.md +57 -0
  125. package/docs/spec/AcceptanceCriteria.v1.md +17 -0
  126. package/docs/spec/AcceptanceEvaluation.v1.md +10 -0
  127. package/docs/spec/AgentEvent.v1.md +47 -0
  128. package/docs/spec/AgentIdentity.v1.md +62 -0
  129. package/docs/spec/AgentPassport.v1.md +95 -0
  130. package/docs/spec/AgentReputation.v1.md +59 -0
  131. package/docs/spec/AgentReputation.v2.md +52 -0
  132. package/docs/spec/AgentRun.v1.md +47 -0
  133. package/docs/spec/AgentRunSettlement.v1.md +52 -0
  134. package/docs/spec/AgentWallet.v1.md +43 -0
  135. package/docs/spec/AgreementDelegation.v1.md +109 -0
  136. package/docs/spec/ArbitrationCase.v1.md +67 -0
  137. package/docs/spec/ArbitrationOutcomeMapping.v1.md +62 -0
  138. package/docs/spec/ArbitrationVerdict.v1.md +60 -0
  139. package/docs/spec/BundleHeadAttestation.v1.md +32 -0
  140. package/docs/spec/CANONICAL_JSON.md +31 -0
  141. package/docs/spec/CRYPTOGRAPHY.md +61 -0
  142. package/docs/spec/ClosePack.v1.md +49 -0
  143. package/docs/spec/ClosePackManifest.v1.md +24 -0
  144. package/docs/spec/DelegationGrant.v1.md +90 -0
  145. package/docs/spec/DisputeCaseLifecycle.v1.md +51 -0
  146. package/docs/spec/DisputeOpenEnvelope.v1.md +43 -0
  147. package/docs/spec/ERRORS.md +76 -0
  148. package/docs/spec/ESCROW_NETTING_INVARIANTS.md +71 -0
  149. package/docs/spec/EvidenceIndex.v1.md +20 -0
  150. package/docs/spec/ExecutionIntent.v1.md +90 -0
  151. package/docs/spec/FinancePackBundleManifest.v1.md +24 -0
  152. package/docs/spec/FundingHold.v1.md +60 -0
  153. package/docs/spec/GovernancePolicy.v1.md +34 -0
  154. package/docs/spec/GovernancePolicy.v2.md +30 -0
  155. package/docs/spec/INVARIANTS.md +389 -0
  156. package/docs/spec/InteractionDirectionMatrix.v1.md +30 -0
  157. package/docs/spec/InvoiceBundleManifest.v1.md +24 -0
  158. package/docs/spec/InvoiceClaim.v1.md +11 -0
  159. package/docs/spec/MONEY_RAIL_STATE_MACHINE.md +58 -0
  160. package/docs/spec/MarketplaceAcceptance.v2.md +46 -0
  161. package/docs/spec/MarketplaceOffer.v2.md +54 -0
  162. package/docs/spec/MeteringReport.v1.md +18 -0
  163. package/docs/spec/OperatorAction.v1.md +90 -0
  164. package/docs/spec/PRODUCER_ERRORS.md +42 -0
  165. package/docs/spec/PolicyDecision.v1.md +83 -0
  166. package/docs/spec/PricingMatrix.v1.md +20 -0
  167. package/docs/spec/PricingMatrixSignatures.v1.md +30 -0
  168. package/docs/spec/PricingMatrixSignatures.v2.md +29 -0
  169. package/docs/spec/ProduceCliOutput.v1.md +46 -0
  170. package/docs/spec/ProofBundleManifest.v1.md +24 -0
  171. package/docs/spec/README.md +109 -0
  172. package/docs/spec/REFERENCE_IMPLEMENTATIONS.md +29 -0
  173. package/docs/spec/REFERENCE_VERIFIER_BEHAVIOR.md +68 -0
  174. package/docs/spec/REMOTE_SIGNER.md +66 -0
  175. package/docs/spec/ReleaseIndex.v1.md +32 -0
  176. package/docs/spec/ReleaseIndexSignatures.v1.md +17 -0
  177. package/docs/spec/ReleaseTrust.v1.md +13 -0
  178. package/docs/spec/ReleaseTrust.v2.md +26 -0
  179. package/docs/spec/RemoteSignerRequest.v1.md +21 -0
  180. package/docs/spec/RemoteSignerResponse.v1.md +16 -0
  181. package/docs/spec/ReputationEvent.v1.md +63 -0
  182. package/docs/spec/RevocationList.v1.md +28 -0
  183. package/docs/spec/SIGNER_PROVIDER_PLUGIN.md +32 -0
  184. package/docs/spec/STRICTNESS.md +68 -0
  185. package/docs/spec/SUPPLY_CHAIN.md +33 -0
  186. package/docs/spec/SettlementAdjustment.v1.md +45 -0
  187. package/docs/spec/SettlementDecisionRecord.v1.md +48 -0
  188. package/docs/spec/SettlementDecisionRecord.v2.md +53 -0
  189. package/docs/spec/SettlementDecisionReport.v1.md +44 -0
  190. package/docs/spec/SettlementKernel.v1.md +59 -0
  191. package/docs/spec/SettlementReceipt.v1.md +63 -0
  192. package/docs/spec/SlaDefinition.v1.md +24 -0
  193. package/docs/spec/SlaEvaluation.v1.md +12 -0
  194. package/docs/spec/THREAT_MODEL.md +113 -0
  195. package/docs/spec/TOOL_PROVENANCE.md +30 -0
  196. package/docs/spec/TRUST_ANCHORS.md +84 -0
  197. package/docs/spec/TenantSettings.v1.md +90 -0
  198. package/docs/spec/TenantSettings.v2.md +99 -0
  199. package/docs/spec/TimestampProof.v1.md +25 -0
  200. package/docs/spec/ToolCallAgreement.v1.md +34 -0
  201. package/docs/spec/ToolCallEvidence.v1.md +47 -0
  202. package/docs/spec/ToolManifest.v1.md +47 -0
  203. package/docs/spec/VERIFIER_ENVIRONMENT.md +38 -0
  204. package/docs/spec/VERSIONING.md +107 -0
  205. package/docs/spec/VerificationReport.v1.md +50 -0
  206. package/docs/spec/VerifyAboutOutput.v1.md +10 -0
  207. package/docs/spec/VerifyCliOutput.v1.md +28 -0
  208. package/docs/spec/WARNINGS.md +83 -0
  209. package/docs/spec/error-codes.v1.txt +285 -0
  210. package/docs/spec/examples/agreement_delegation_v1.example.json +21 -0
  211. package/docs/spec/examples/arbitration_case_v1.example.json +26 -0
  212. package/docs/spec/examples/arbitration_verdict_v1.example.json +32 -0
  213. package/docs/spec/examples/dispute_open_envelope_v1.example.json +18 -0
  214. package/docs/spec/examples/produce_cli_output_v1.example.json +32 -0
  215. package/docs/spec/examples/release_index_signature_v1.example.json +9 -0
  216. package/docs/spec/examples/release_index_signatures_v1.example.json +14 -0
  217. package/docs/spec/examples/release_index_v1.example.json +15 -0
  218. package/docs/spec/examples/release_trust_v1.example.json +7 -0
  219. package/docs/spec/examples/release_trust_v2.example.json +22 -0
  220. package/docs/spec/examples/remote_signer_request_v1.example.json +18 -0
  221. package/docs/spec/examples/remote_signer_response_v1.example.json +8 -0
  222. package/docs/spec/examples/reputation_event_v1.example.json +29 -0
  223. package/docs/spec/examples/verification_report_v1.example.json +24 -0
  224. package/docs/spec/examples/verify_about_output_v1.example.json +29 -0
  225. package/docs/spec/examples/verify_cli_output_v1.example.json +13 -0
  226. package/docs/spec/legacy/MarketplaceAcceptance.v1.md +48 -0
  227. package/docs/spec/legacy/MarketplaceOffer.v1.md +56 -0
  228. package/docs/spec/legacy/schemas/MarketplaceAcceptance.v1.schema.json +53 -0
  229. package/docs/spec/legacy/schemas/MarketplaceOffer.v1.schema.json +61 -0
  230. package/docs/spec/producer-error-codes.v1.txt +14 -0
  231. package/docs/spec/schemas/AcceptanceCriteria.v1.schema.json +24 -0
  232. package/docs/spec/schemas/AcceptanceEvaluation.v1.schema.json +26 -0
  233. package/docs/spec/schemas/AgentEvent.v1.schema.json +49 -0
  234. package/docs/spec/schemas/AgentIdentity.v1.schema.json +129 -0
  235. package/docs/spec/schemas/AgentPassport.v1.schema.json +112 -0
  236. package/docs/spec/schemas/AgentReputation.v1.schema.json +151 -0
  237. package/docs/spec/schemas/AgentReputation.v2.schema.json +120 -0
  238. package/docs/spec/schemas/AgentRun.v1.schema.json +71 -0
  239. package/docs/spec/schemas/AgentRunSettlement.v1.schema.json +75 -0
  240. package/docs/spec/schemas/AgentWallet.v1.schema.json +54 -0
  241. package/docs/spec/schemas/AgreementDelegation.v1.schema.json +50 -0
  242. package/docs/spec/schemas/ArbitrationCase.v1.schema.json +133 -0
  243. package/docs/spec/schemas/ArbitrationVerdict.v1.schema.json +149 -0
  244. package/docs/spec/schemas/BundleHeadAttestation.v1.schema.json +21 -0
  245. package/docs/spec/schemas/ClosePackManifest.v1.schema.json +38 -0
  246. package/docs/spec/schemas/DelegationGrant.v1.schema.json +102 -0
  247. package/docs/spec/schemas/DisputeOpenEnvelope.v1.schema.json +78 -0
  248. package/docs/spec/schemas/EvidenceIndex.v1.schema.json +41 -0
  249. package/docs/spec/schemas/ExecutionIntent.v1.schema.json +85 -0
  250. package/docs/spec/schemas/FinancePackBundleManifest.v1.schema.json +38 -0
  251. package/docs/spec/schemas/FundingHold.v1.schema.json +46 -0
  252. package/docs/spec/schemas/GovernancePolicy.v1.schema.json +45 -0
  253. package/docs/spec/schemas/GovernancePolicy.v2.schema.json +70 -0
  254. package/docs/spec/schemas/InteractionDirectionMatrix.v1.schema.json +43 -0
  255. package/docs/spec/schemas/InvoiceBundleManifest.v1.schema.json +38 -0
  256. package/docs/spec/schemas/InvoiceClaim.v1.schema.json +39 -0
  257. package/docs/spec/schemas/MarketplaceAcceptance.v2.schema.json +53 -0
  258. package/docs/spec/schemas/MarketplaceOffer.v2.schema.json +61 -0
  259. package/docs/spec/schemas/MeteringReport.v1.schema.json +45 -0
  260. package/docs/spec/schemas/OperatorAction.v1.schema.json +113 -0
  261. package/docs/spec/schemas/PolicyDecision.v1.schema.json +74 -0
  262. package/docs/spec/schemas/PricingMatrix.v1.schema.json +24 -0
  263. package/docs/spec/schemas/PricingMatrixSignatures.v1.schema.json +24 -0
  264. package/docs/spec/schemas/PricingMatrixSignatures.v2.schema.json +24 -0
  265. package/docs/spec/schemas/ProduceCliOutput.v1.schema.json +107 -0
  266. package/docs/spec/schemas/ProofBundleManifest.v1.schema.json +37 -0
  267. package/docs/spec/schemas/PublicKeys.v1.schema.json +33 -0
  268. package/docs/spec/schemas/ReleaseIndex.v1.schema.json +45 -0
  269. package/docs/spec/schemas/ReleaseIndexSignature.v1.schema.json +16 -0
  270. package/docs/spec/schemas/ReleaseIndexSignatures.v1.schema.json +16 -0
  271. package/docs/spec/schemas/ReleaseTrust.v1.schema.json +15 -0
  272. package/docs/spec/schemas/ReleaseTrust.v2.schema.json +37 -0
  273. package/docs/spec/schemas/RemoteSignerPublicKeyResponse.v1.schema.json +14 -0
  274. package/docs/spec/schemas/RemoteSignerRequest.v1.schema.json +24 -0
  275. package/docs/spec/schemas/RemoteSignerResponse.v1.schema.json +10 -0
  276. package/docs/spec/schemas/RemoteSignerSignRequest.v1.schema.json +27 -0
  277. package/docs/spec/schemas/RemoteSignerSignResponse.v1.schema.json +16 -0
  278. package/docs/spec/schemas/ReputationEvent.v1.schema.json +164 -0
  279. package/docs/spec/schemas/RevocationList.v1.schema.json +51 -0
  280. package/docs/spec/schemas/SettlementAdjustment.v1.schema.json +44 -0
  281. package/docs/spec/schemas/SettlementDecisionRecord.v1.schema.json +66 -0
  282. package/docs/spec/schemas/SettlementDecisionRecord.v2.schema.json +149 -0
  283. package/docs/spec/schemas/SettlementDecisionReport.v1.schema.json +61 -0
  284. package/docs/spec/schemas/SettlementReceipt.v1.schema.json +135 -0
  285. package/docs/spec/schemas/SlaDefinition.v1.schema.json +33 -0
  286. package/docs/spec/schemas/SlaEvaluation.v1.schema.json +26 -0
  287. package/docs/spec/schemas/TenantSettings.v1.schema.json +90 -0
  288. package/docs/spec/schemas/TenantSettings.v2.schema.json +161 -0
  289. package/docs/spec/schemas/TimestampProof.v1.schema.json +17 -0
  290. package/docs/spec/schemas/ToolCallAgreement.v1.schema.json +34 -0
  291. package/docs/spec/schemas/ToolCallEvidence.v1.schema.json +45 -0
  292. package/docs/spec/schemas/ToolManifest.v1.schema.json +54 -0
  293. package/docs/spec/schemas/VerificationReport.v1.schema.json +83 -0
  294. package/docs/spec/schemas/VerifyAboutOutput.v1.schema.json +54 -0
  295. package/docs/spec/schemas/VerifyCliOutput.v1.schema.json +75 -0
  296. package/docs/spec/schemas/VerifyReleaseOutput.v1.schema.json +47 -0
  297. package/docs/spec/x402-error-codes.v1.txt +35 -0
  298. package/docs/templates/buyer-email.txt +18 -0
  299. package/docs/templates/buyer-one-pager.md +24 -0
  300. package/package.json +53 -6
  301. package/scripts/acceptance/full-stack.mjs +734 -0
  302. package/scripts/acceptance/full-stack.sh +99 -0
  303. package/scripts/audit/build-audit-packet.mjs +242 -0
  304. package/scripts/backup-pg.sh +45 -0
  305. package/scripts/backup-restore/README.md +18 -0
  306. package/scripts/backup-restore/capture-state.mjs +130 -0
  307. package/scripts/backup-restore/client.mjs +97 -0
  308. package/scripts/backup-restore/seed-workload.mjs +235 -0
  309. package/scripts/backup-restore/verify-state.mjs +139 -0
  310. package/scripts/backup-restore-test.sh +217 -0
  311. package/scripts/chaos.js +221 -0
  312. package/scripts/ci/build-launch-cutover-packet.mjs +304 -0
  313. package/scripts/ci/build-self-serve-benchmark-report.mjs +122 -0
  314. package/scripts/ci/changelog-guard.mjs +145 -0
  315. package/scripts/ci/check-kernel-v0-launch-gate.mjs +233 -0
  316. package/scripts/ci/check-secret-hygiene.mjs +78 -0
  317. package/scripts/ci/check-version-consistency.mjs +42 -0
  318. package/scripts/ci/cli-pack-smoke.mjs +160 -0
  319. package/scripts/ci/flake-budget-guard.mjs +68 -0
  320. package/scripts/ci/generate-error-codes.mjs +54 -0
  321. package/scripts/ci/lib/lighthouse-tracker.mjs +90 -0
  322. package/scripts/ci/lib/self-serve-launch-gate.mjs +89 -0
  323. package/scripts/ci/npm-pack-smoke.mjs +454 -0
  324. package/scripts/ci/run-10x-throughput-drill.mjs +318 -0
  325. package/scripts/ci/run-10x-throughput-incident-rehearsal.mjs +368 -0
  326. package/scripts/ci/run-arbitration-workspace-browser-e2e.sh +22 -0
  327. package/scripts/ci/run-circle-sandbox-smoke.mjs +237 -0
  328. package/scripts/ci/run-go-live-gate.mjs +150 -0
  329. package/scripts/ci/run-kernel-v0-ship-gate.mjs +97 -0
  330. package/scripts/ci/run-mcp-host-cert-matrix.mjs +201 -0
  331. package/scripts/ci/run-mcp-host-smoke.mjs +473 -0
  332. package/scripts/ci/run-offline-verification-parity-gate.mjs +762 -0
  333. package/scripts/ci/run-onboarding-host-success-gate.mjs +516 -0
  334. package/scripts/ci/run-onboarding-policy-slo-gate.mjs +537 -0
  335. package/scripts/ci/run-production-cutover-gate.mjs +540 -0
  336. package/scripts/ci/run-public-openclaw-npx-smoke.mjs +148 -0
  337. package/scripts/ci/run-release-promotion-guard.mjs +756 -0
  338. package/scripts/ci/run-self-serve-launch-gate.mjs +56 -0
  339. package/scripts/ci/runtime-import-smoke.mjs +58 -0
  340. package/scripts/ci/update-lighthouse-tracker.mjs +112 -0
  341. package/scripts/closepack/lib.mjs +286 -0
  342. package/scripts/collect-debug.sh +263 -0
  343. package/scripts/demo/compositional-settlement-3hop.mjs +237 -0
  344. package/scripts/demo/delivery-robot/export-ui-fixture.mjs +188 -0
  345. package/scripts/demo/delivery-robot/generate.mjs +377 -0
  346. package/scripts/demo/kernel-agent-goes-shopping.mjs +202 -0
  347. package/scripts/demo/magic-link-first-green.mjs +118 -0
  348. package/scripts/demo/magic-link-kind-smoke.mjs +577 -0
  349. package/scripts/demo/mcp-paid-exa.mjs +1110 -0
  350. package/scripts/dev/billing-doctor.sh +145 -0
  351. package/scripts/dev/billing-smoke-prod.sh +219 -0
  352. package/scripts/dev/billing-webhook-replay.sh +161 -0
  353. package/scripts/dev/env.dev.example +29 -0
  354. package/scripts/dev/env.sh +37 -0
  355. package/scripts/dev/new-sdk-key.sh +81 -0
  356. package/scripts/dev/sdk-first-run.sh +21 -0
  357. package/scripts/dev/smoke-x402-gateway.sh +115 -0
  358. package/scripts/dev/start-api.sh +24 -0
  359. package/scripts/doctor/mcp-host.mjs +120 -0
  360. package/scripts/examples/produce-and-verify-jobproof.mjs +191 -0
  361. package/scripts/examples/sdk-first-paid-rfq.py +105 -0
  362. package/scripts/examples/sdk-first-verified-run.mjs +85 -0
  363. package/scripts/examples/sdk-first-verified-run.py +99 -0
  364. package/scripts/examples/sdk-tenant-analytics.mjs +103 -0
  365. package/scripts/examples/sdk-tenant-analytics.py +118 -0
  366. package/scripts/finance-pack/bundle.mjs +284 -0
  367. package/scripts/fixtures/generate-bundle-fixtures.mjs +877 -0
  368. package/scripts/governance/export.mjs +169 -0
  369. package/scripts/load/delivery-stress.k6.js +183 -0
  370. package/scripts/load/ingest-burst.k6.js +236 -0
  371. package/scripts/load/run-delivery-load.js +66 -0
  372. package/scripts/load/webhook-receiver.js +131 -0
  373. package/scripts/magic-link/migrate-run-records-to-db.mjs +35 -0
  374. package/scripts/mcp/probe.mjs +238 -0
  375. package/scripts/mcp/settld-mcp-http-gateway.mjs +178 -0
  376. package/scripts/mcp/settld-mcp-server.mjs +1511 -0
  377. package/scripts/openapi/write.mjs +13 -0
  378. package/scripts/ops/bootstrap-tenant-conformance.mjs +185 -0
  379. package/scripts/ops/build-x402-pilot-reliability-report.mjs +489 -0
  380. package/scripts/ops/check-x402-receipt-sample.mjs +181 -0
  381. package/scripts/ops/design-partner-run-packet.mjs +466 -0
  382. package/scripts/ops/dispute-finance-reconciliation-packet.mjs +313 -0
  383. package/scripts/ops/hosted-baseline-evidence.mjs +890 -0
  384. package/scripts/ops/money-rails-chargeback-evidence.mjs +509 -0
  385. package/scripts/ops/money-rails-reconcile-evidence.mjs +180 -0
  386. package/scripts/ops/p0-seed-money-rail-operation.mjs +432 -0
  387. package/scripts/ops/run-x402-hitl-smoke.mjs +607 -0
  388. package/scripts/pilot/finance-pack.mjs +495 -0
  389. package/scripts/pilot/fixtures/robot-keypair.json +4 -0
  390. package/scripts/pilot/fixtures/server-signer.json +4 -0
  391. package/scripts/policy/cli.mjs +600 -0
  392. package/scripts/profile/cli.mjs +1324 -0
  393. package/scripts/proof-bundle/job.mjs +109 -0
  394. package/scripts/proof-bundle/lib.mjs +92 -0
  395. package/scripts/proof-bundle/month.mjs +103 -0
  396. package/scripts/provider/conformance-run.mjs +159 -0
  397. package/scripts/provider/keys-generate.mjs +135 -0
  398. package/scripts/provider/publish.mjs +420 -0
  399. package/scripts/quickstart/x402.mjs +334 -0
  400. package/scripts/register-entity-secret.mjs +102 -0
  401. package/scripts/release/build-artifacts.mjs +181 -0
  402. package/scripts/release/generate-release-index.mjs +112 -0
  403. package/scripts/release/release-index-lib.mjs +232 -0
  404. package/scripts/release/sign-release-index.mjs +85 -0
  405. package/scripts/release/validate-release-assets.mjs +170 -0
  406. package/scripts/release/verify-release.mjs +261 -0
  407. package/scripts/restore-pg.sh +34 -0
  408. package/scripts/scaffold/create-settld-paid-tool.mjs +19 -0
  409. package/scripts/sdk/smoke-python.py +30 -0
  410. package/scripts/sdk/smoke.mjs +16 -0
  411. package/scripts/settlement/x402-batch-worker.mjs +1091 -0
  412. package/scripts/setup/circle-bootstrap.mjs +310 -0
  413. package/scripts/setup/host-config.mjs +617 -0
  414. package/scripts/setup/onboard.mjs +1337 -0
  415. package/scripts/setup/openclaw-onboard.mjs +423 -0
  416. package/scripts/setup/wizard.mjs +986 -0
  417. package/scripts/slo/check.mjs +239 -0
  418. package/scripts/smoke/k8s-smoke.mjs +214 -0
  419. package/scripts/spec/generate-protocol-vectors.mjs +1019 -0
  420. package/scripts/test/check-no-generated-artifacts.sh +12 -0
  421. package/scripts/test/run.sh +59 -0
  422. package/scripts/trust/validate-trust-file.mjs +57 -0
  423. package/scripts/trust-config/rotate-settld-pay.mjs +277 -0
  424. package/scripts/trust-config/wizard.mjs +161 -0
  425. package/scripts/vendor-contract-test-lib.mjs +182 -0
  426. package/scripts/vendor-contract-test.mjs +55 -0
  427. package/scripts/vercel/build-mkdocs.sh +9 -0
  428. package/scripts/vercel/ignore-mkdocs.sh +25 -0
  429. package/scripts/vercel/install-mkdocs.sh +6 -0
  430. package/scripts/verify-pg.js +217 -0
  431. package/scripts/x402/receipt-verify.mjs +289 -0
  432. package/services/finance-sink/src/dedupe-store.js +29 -6
  433. package/services/receiver/src/dedupe-store.js +29 -5
  434. package/services/x402-gateway/Dockerfile +13 -0
  435. package/services/x402-gateway/README.md +58 -0
  436. package/services/x402-gateway/examples/upstream-mock.js +337 -0
  437. package/services/x402-gateway/src/server.js +1058 -0
  438. package/src/api/app.js +34658 -16940
  439. package/src/api/maintenance.js +70 -0
  440. package/src/api/middleware/trust-kernel.js +114 -0
  441. package/src/api/openapi.js +1778 -70
  442. package/src/api/persistence.js +456 -0
  443. package/src/api/server.js +81 -5
  444. package/src/api/store.js +1581 -62
  445. package/src/api/workers/deliveries.js +99 -4
  446. package/src/api/workers/insolvency-sweep.js +159 -0
  447. package/src/core/agent-card.js +69 -0
  448. package/src/core/agent-wallets.js +231 -0
  449. package/src/core/agreement-delegation.js +549 -0
  450. package/src/core/billing-plans.js +40 -6
  451. package/src/core/circle-reserve-adapter.js +845 -0
  452. package/src/core/event-policy.js +21 -2
  453. package/src/core/maintenance-locks.js +1 -0
  454. package/src/core/operator-action.js +303 -0
  455. package/src/core/paid-tool-manifest.js +318 -0
  456. package/src/core/policy-decision.js +322 -0
  457. package/src/core/policy-packs.js +207 -0
  458. package/src/core/profile-fingerprint.js +27 -0
  459. package/src/core/profile-simulation-reasons.js +84 -0
  460. package/src/core/profile-templates.js +242 -0
  461. package/src/core/provider-publish-conformance.js +525 -0
  462. package/src/core/provider-publish-proof.js +396 -0
  463. package/src/core/provider-quote-signature.js +170 -0
  464. package/src/core/settld-keys.js +112 -0
  465. package/src/core/settld-pay-token.js +344 -0
  466. package/src/core/settlement-kernel.js +239 -2
  467. package/src/core/settlement-verifier.js +335 -0
  468. package/src/core/tool-call-agreement.js +112 -0
  469. package/src/core/tool-call-evidence.js +144 -0
  470. package/src/core/tool-provider-signature.js +98 -0
  471. package/src/core/wallet-assignment-resolver.js +129 -0
  472. package/src/core/wallet-provider-bootstrap.js +365 -0
  473. package/src/core/x402-escalation-override.js +258 -0
  474. package/src/core/x402-gate.js +118 -0
  475. package/src/core/x402-provider-refund-decision.js +220 -0
  476. package/src/core/x402-receipt-verifier.js +708 -0
  477. package/src/core/x402-reversal-command.js +251 -0
  478. package/src/core/x402-wallet-issuer-decision.js +252 -0
  479. package/src/core/zk-verifier.js +300 -0
  480. package/src/db/migrations/029_reputation_event_index.sql +54 -0
  481. package/src/db/migrations/030_artifacts_source_event_unique_job_only.sql +15 -0
  482. package/src/db/pg.js +18 -7
  483. package/src/db/store-pg.js +1508 -111
@@ -0,0 +1,1058 @@
1
+ import http from "node:http";
2
+ import crypto from "node:crypto";
3
+ import { URL } from "node:url";
4
+ import { Readable } from "node:stream";
5
+
6
+ import { parseX402PaymentRequired } from "../../../src/core/x402-gate.js";
7
+ import { canonicalJsonStringify } from "../../../src/core/canonical-json.js";
8
+ import { keyIdFromPublicKeyPem } from "../../../src/core/crypto.js";
9
+ import { normalizeReasonCodes as normalizePolicyDecisionReasonCodes } from "../../../src/core/policy-decision.js";
10
+ import { buildToolProviderQuotePayloadV1, verifyToolProviderQuoteSignatureV1 } from "../../../src/core/provider-quote-signature.js";
11
+ import { computeSettldPayRequestBindingSha256V1 } from "../../../src/core/settld-pay-token.js";
12
+ import { computeToolProviderSignaturePayloadHashV1, verifyToolProviderSignatureV1 } from "../../../src/core/tool-provider-signature.js";
13
+
14
+ function readRequiredEnv(name) {
15
+ const raw = process.env[name];
16
+ if (typeof raw !== "string" || raw.trim() === "") throw new Error(`${name} is required`);
17
+ return raw.trim();
18
+ }
19
+
20
+ function readOptionalIntEnv(name, fallback) {
21
+ const raw = process.env[name];
22
+ if (raw === null || raw === undefined || String(raw).trim() === "") return fallback;
23
+ const n = Number(raw);
24
+ if (!Number.isSafeInteger(n)) throw new Error(`${name} must be an integer`);
25
+ return n;
26
+ }
27
+
28
+ function readOptionalBoolEnv(name, fallback) {
29
+ const raw = process.env[name];
30
+ if (raw === null || raw === undefined || String(raw).trim() === "") return fallback;
31
+ const v = String(raw).trim().toLowerCase();
32
+ if (v === "1" || v === "true" || v === "yes") return true;
33
+ if (v === "0" || v === "false" || v === "no") return false;
34
+ throw new Error(`${name} must be a boolean (1/0/true/false)`);
35
+ }
36
+
37
+ function readOptionalStringEnv(name, fallback = null) {
38
+ const raw = process.env[name];
39
+ if (raw === null || raw === undefined || String(raw).trim() === "") return fallback;
40
+ return String(raw).trim();
41
+ }
42
+
43
+ function sanitizeIdSegment(text, { maxLen = 96 } = {}) {
44
+ const raw = String(text ?? "").trim();
45
+ const safe = raw.replaceAll(/[^A-Za-z0-9:_-]/g, "_").slice(0, maxLen);
46
+ return safe || "unknown";
47
+ }
48
+
49
+ function parseCacheControlMaxAgeMs(value, fallbackMs) {
50
+ const raw = typeof value === "string" ? value : "";
51
+ const m = raw.match(/max-age\s*=\s*(\d+)/i);
52
+ if (!m) return fallbackMs;
53
+ const sec = Number(m[1]);
54
+ if (!Number.isSafeInteger(sec) || sec < 0) return fallbackMs;
55
+ return sec * 1000;
56
+ }
57
+
58
+ function normalizeOfferRef(value, { maxLen = 200 } = {}) {
59
+ if (value === null || value === undefined || String(value).trim() === "") return null;
60
+ const out = String(value).trim();
61
+ if (out.length > maxLen) return null;
62
+ if (!/^[A-Za-z0-9:_-]+$/.test(out)) return null;
63
+ return out;
64
+ }
65
+
66
+ function sha256Hex(buf) {
67
+ return crypto.createHash("sha256").update(buf).digest("hex");
68
+ }
69
+
70
+ function jwkToSpkiPem(jwk) {
71
+ if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) return null;
72
+ if (String(jwk.kty ?? "") !== "OKP" || String(jwk.crv ?? "") !== "Ed25519") return null;
73
+ if (typeof jwk.x !== "string" || jwk.x.trim() === "") return null;
74
+ try {
75
+ const key = crypto.createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: String(jwk.x).trim() }, format: "jwk" });
76
+ return key.export({ format: "pem", type: "spki" }).toString().trim();
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ function stableIdemKey(prefix, input) {
83
+ const h = sha256Hex(Buffer.from(String(input ?? ""), "utf8")).slice(0, 32);
84
+ return `${prefix}_${h}`;
85
+ }
86
+
87
+ function extractAmountAndCurrency(fields) {
88
+ if (!fields || typeof fields !== "object") return { ok: false, error: "missing_fields" };
89
+ const keys = ["amountCents", "amount_cents", "priceCents", "price_cents", "price", "amount"];
90
+ let amountCents = null;
91
+ for (const k of keys) {
92
+ if (fields[k] === null || fields[k] === undefined) continue;
93
+ const n = Number(fields[k]);
94
+ if (Number.isSafeInteger(n) && n > 0) {
95
+ amountCents = n;
96
+ break;
97
+ }
98
+ }
99
+ if (amountCents === null) return { ok: false, error: "amount_not_found" };
100
+ const currencyRaw = fields.currency ?? fields.ccy ?? "USD";
101
+ const currency = String(currencyRaw ?? "USD")
102
+ .trim()
103
+ .toUpperCase();
104
+ return { ok: true, amountCents, currency: currency || "USD" };
105
+ }
106
+
107
+ function normalizeOfferBool(value, { fallback = false } = {}) {
108
+ if (value === null || value === undefined || String(value).trim() === "") return fallback;
109
+ const raw = String(value).trim().toLowerCase();
110
+ if (raw === "1" || raw === "true" || raw === "yes" || raw === "on") return true;
111
+ if (raw === "0" || raw === "false" || raw === "no" || raw === "off") return false;
112
+ return fallback;
113
+ }
114
+
115
+ function normalizeStrictRequestBindingMode(value) {
116
+ if (value === null || value === undefined || String(value).trim() === "") return null;
117
+ const raw = String(value).trim().toLowerCase();
118
+ return raw === "strict" ? "strict" : null;
119
+ }
120
+
121
+ function computeStrictRequestBindingSha256ForRetry({ reqMethod, upstreamUrl }) {
122
+ const method = String(reqMethod ?? "GET").toUpperCase();
123
+ const host = String(upstreamUrl.host ?? "").trim().toLowerCase();
124
+ const pathWithQuery = `${upstreamUrl.pathname}${upstreamUrl.search}`;
125
+ const emptyBodySha256 = sha256Hex(Buffer.from("", "utf8"));
126
+ return computeSettldPayRequestBindingSha256V1({ method, host, pathWithQuery, bodySha256: emptyBodySha256 });
127
+ }
128
+
129
+ function createProviderKeyResolver({
130
+ providerPublicKeyPem = null,
131
+ providerJwksUrl = null,
132
+ defaultMaxAgeMs = 300_000,
133
+ fetchTimeoutMs = 3_000
134
+ } = {}) {
135
+ const staticPem = typeof providerPublicKeyPem === "string" && providerPublicKeyPem.trim() !== "" ? providerPublicKeyPem.trim() : null;
136
+ const staticKid = staticPem ? keyIdFromPublicKeyPem(staticPem) : null;
137
+ const jwksUrl = typeof providerJwksUrl === "string" && providerJwksUrl.trim() !== "" ? providerJwksUrl.trim() : null;
138
+ const normalizedDefaultMaxAgeMs = Number.isSafeInteger(Number(defaultMaxAgeMs)) && Number(defaultMaxAgeMs) > 0 ? Number(defaultMaxAgeMs) : 300_000;
139
+ const normalizedFetchTimeoutMs = Number.isSafeInteger(Number(fetchTimeoutMs)) && Number(fetchTimeoutMs) > 0 ? Number(fetchTimeoutMs) : 3_000;
140
+
141
+ const cache = {
142
+ keysById: new Map(staticKid ? [[staticKid, { publicKeyPem: staticPem, source: "static" }]] : []),
143
+ expiresAtMs: 0
144
+ };
145
+
146
+ async function refresh() {
147
+ if (!jwksUrl) return false;
148
+ const signal = typeof AbortSignal?.timeout === "function" ? AbortSignal.timeout(normalizedFetchTimeoutMs) : undefined;
149
+ const res = await fetch(jwksUrl, { method: "GET", ...(signal ? { signal } : {}) });
150
+ if (!res.ok) throw new Error(`provider jwks fetch failed (${res.status})`);
151
+ const payload = await res.json();
152
+ const rows = Array.isArray(payload?.keys) ? payload.keys : [];
153
+ const keysById = new Map();
154
+ for (const row of rows) {
155
+ const publicKeyPem = jwkToSpkiPem(row);
156
+ if (!publicKeyPem) continue;
157
+ const derivedKid = keyIdFromPublicKeyPem(publicKeyPem);
158
+ keysById.set(derivedKid, { publicKeyPem, source: "jwks", kid: derivedKid });
159
+ const rowKid = normalizeOfferRef(row?.kid, { maxLen: 200 });
160
+ if (rowKid && rowKid !== derivedKid) {
161
+ keysById.set(rowKid, { publicKeyPem, source: "jwks", kid: rowKid });
162
+ }
163
+ }
164
+ if (staticKid && staticPem && !keysById.has(staticKid)) {
165
+ keysById.set(staticKid, { publicKeyPem: staticPem, source: "static", kid: staticKid });
166
+ }
167
+ if (keysById.size > 0) {
168
+ cache.keysById = keysById;
169
+ cache.expiresAtMs = Date.now() + parseCacheControlMaxAgeMs(res.headers.get("cache-control"), normalizedDefaultMaxAgeMs);
170
+ return true;
171
+ }
172
+ if (staticKid && staticPem) {
173
+ cache.keysById = new Map([[staticKid, { publicKeyPem: staticPem, source: "static", kid: staticKid }]]);
174
+ cache.expiresAtMs = Date.now() + normalizedDefaultMaxAgeMs;
175
+ return true;
176
+ }
177
+ return false;
178
+ }
179
+
180
+ return {
181
+ enabled: Boolean(staticPem || jwksUrl),
182
+ staticKeyId: staticKid,
183
+ async resolveByKeyId(keyId) {
184
+ const wantedKeyId = normalizeOfferRef(keyId, { maxLen: 200 });
185
+ const nowMs = Date.now();
186
+ const keyFromCache = wantedKeyId ? cache.keysById.get(wantedKeyId) ?? null : null;
187
+ if (keyFromCache && cache.expiresAtMs > nowMs) return keyFromCache;
188
+ if (cache.expiresAtMs <= nowMs || (wantedKeyId && !cache.keysById.has(wantedKeyId))) {
189
+ try {
190
+ await refresh();
191
+ } catch {
192
+ // keep stale cache and/or static fallback
193
+ }
194
+ }
195
+ if (wantedKeyId) {
196
+ const resolved = cache.keysById.get(wantedKeyId);
197
+ if (resolved) return resolved;
198
+ }
199
+ if (staticKid && staticPem && (!wantedKeyId || wantedKeyId === staticKid)) {
200
+ return { publicKeyPem: staticPem, source: "static", kid: staticKid };
201
+ }
202
+ return null;
203
+ }
204
+ };
205
+ }
206
+
207
+ function parseBase64UrlJson(rawValue) {
208
+ const raw = typeof rawValue === "string" ? rawValue.trim() : "";
209
+ if (!raw) return null;
210
+ try {
211
+ const text = Buffer.from(raw, "base64url").toString("utf8");
212
+ const parsed = JSON.parse(text);
213
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
214
+ return parsed;
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+
220
+ function parseProviderQuoteHeaders(headers) {
221
+ return {
222
+ quote: parseBase64UrlJson(headers?.["x-settld-provider-quote"] ?? headers?.["X-Settld-Provider-Quote"] ?? null),
223
+ signature: parseBase64UrlJson(
224
+ headers?.["x-settld-provider-quote-signature"] ?? headers?.["X-Settld-Provider-Quote-Signature"] ?? null
225
+ )
226
+ };
227
+ }
228
+
229
+ function parseAgentPassportHeader(headers) {
230
+ const rawHeader = headers?.["x-settld-agent-passport"] ?? headers?.["X-Settld-Agent-Passport"] ?? null;
231
+ const raw = typeof rawHeader === "string" ? rawHeader.trim() : Array.isArray(rawHeader) ? String(rawHeader[0] ?? "").trim() : "";
232
+ if (!raw) return { ok: true, agentPassport: null };
233
+ let text = null;
234
+ try {
235
+ if (raw.startsWith("{")) {
236
+ text = raw;
237
+ } else {
238
+ text = Buffer.from(raw, "base64url").toString("utf8");
239
+ }
240
+ } catch {
241
+ return { ok: false, message: "x-settld-agent-passport must be base64url JSON or raw JSON object" };
242
+ }
243
+ try {
244
+ const parsed = JSON.parse(text);
245
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
246
+ return { ok: false, message: "x-settld-agent-passport must decode to a JSON object" };
247
+ }
248
+ return { ok: true, agentPassport: parsed };
249
+ } catch {
250
+ return { ok: false, message: "x-settld-agent-passport is not valid JSON" };
251
+ }
252
+ }
253
+
254
+ async function verifyProviderQuoteChallenge({
255
+ offerFields,
256
+ amountCents,
257
+ currency,
258
+ requestBindingMode,
259
+ requestBindingSha256,
260
+ providerKeyResolver,
261
+ quoteHeaders
262
+ } = {}) {
263
+ if (!providerKeyResolver?.enabled) {
264
+ return { ok: true, quote: null };
265
+ }
266
+ const quote = quoteHeaders?.quote;
267
+ const signature = quoteHeaders?.signature;
268
+ if (!quote || !signature) {
269
+ return {
270
+ ok: false,
271
+ code: "X402_PROVIDER_QUOTE_SIGNATURE_MISSING",
272
+ message: "provider quote signature is required but missing"
273
+ };
274
+ }
275
+ let normalizedQuote;
276
+ try {
277
+ normalizedQuote = buildToolProviderQuotePayloadV1(quote);
278
+ } catch (err) {
279
+ return {
280
+ ok: false,
281
+ code: "X402_PROVIDER_QUOTE_INVALID",
282
+ message: err?.message ?? "provider quote payload invalid"
283
+ };
284
+ }
285
+ const signatureKeyId = normalizeOfferRef(signature?.keyId, { maxLen: 200 });
286
+ const providerKey = signatureKeyId ? await providerKeyResolver.resolveByKeyId(signatureKeyId) : null;
287
+ if (!providerKey?.publicKeyPem) {
288
+ return {
289
+ ok: false,
290
+ code: "X402_PROVIDER_QUOTE_KEY_ID_UNKNOWN",
291
+ message: "provider quote key id is unknown"
292
+ };
293
+ }
294
+ let signatureValid = false;
295
+ try {
296
+ signatureValid = verifyToolProviderQuoteSignatureV1({
297
+ quote: normalizedQuote,
298
+ signature,
299
+ publicKeyPem: providerKey.publicKeyPem
300
+ });
301
+ } catch {
302
+ signatureValid = false;
303
+ }
304
+ if (!signatureValid) {
305
+ return {
306
+ ok: false,
307
+ code: "X402_PROVIDER_QUOTE_SIGNATURE_INVALID",
308
+ message: "provider quote signature verification failed"
309
+ };
310
+ }
311
+
312
+ const offerProviderId = normalizeOfferRef(offerFields?.providerId);
313
+ const offerToolId = normalizeOfferRef(offerFields?.toolId);
314
+ const offerQuoteId = normalizeOfferRef(offerFields?.quoteId);
315
+ const offerQuoteRequired = normalizeOfferBool(offerFields?.quoteRequired, { fallback: false });
316
+ const offerSpendAuthorizationMode = normalizeOfferRef(offerFields?.spendAuthorizationMode, { maxLen: 32 });
317
+ const expectedBindingMode = requestBindingMode ?? "none";
318
+
319
+ if (offerProviderId && normalizedQuote.providerId !== offerProviderId) {
320
+ return { ok: false, code: "X402_PROVIDER_QUOTE_PROVIDER_MISMATCH", message: "provider quote providerId mismatch" };
321
+ }
322
+ if (offerToolId && normalizedQuote.toolId !== offerToolId) {
323
+ return { ok: false, code: "X402_PROVIDER_QUOTE_TOOL_MISMATCH", message: "provider quote toolId mismatch" };
324
+ }
325
+ if (normalizedQuote.amountCents !== amountCents) {
326
+ return { ok: false, code: "X402_PROVIDER_QUOTE_AMOUNT_MISMATCH", message: "provider quote amount mismatch" };
327
+ }
328
+ if (String(normalizedQuote.currency).toUpperCase() !== String(currency).toUpperCase()) {
329
+ return { ok: false, code: "X402_PROVIDER_QUOTE_CURRENCY_MISMATCH", message: "provider quote currency mismatch" };
330
+ }
331
+ if (normalizedQuote.requestBindingMode !== expectedBindingMode) {
332
+ return {
333
+ ok: false,
334
+ code: "X402_PROVIDER_QUOTE_BINDING_MODE_MISMATCH",
335
+ message: "provider quote request binding mode mismatch"
336
+ };
337
+ }
338
+ if (expectedBindingMode === "strict") {
339
+ if (String(normalizedQuote.requestBindingSha256 ?? "") !== String(requestBindingSha256 ?? "")) {
340
+ return {
341
+ ok: false,
342
+ code: "X402_PROVIDER_QUOTE_BINDING_HASH_MISMATCH",
343
+ message: "provider quote request binding hash mismatch"
344
+ };
345
+ }
346
+ }
347
+ if (offerQuoteRequired && normalizedQuote.quoteRequired !== true) {
348
+ return { ok: false, code: "X402_PROVIDER_QUOTE_REQUIRED_MISMATCH", message: "provider quoteRequired mismatch" };
349
+ }
350
+ if (offerQuoteId && String(normalizedQuote.quoteId ?? "") !== offerQuoteId) {
351
+ return { ok: false, code: "X402_PROVIDER_QUOTE_ID_MISMATCH", message: "provider quoteId mismatch" };
352
+ }
353
+ if (offerSpendAuthorizationMode && String(normalizedQuote.spendAuthorizationMode ?? "") !== offerSpendAuthorizationMode) {
354
+ return {
355
+ ok: false,
356
+ code: "X402_PROVIDER_QUOTE_SPEND_AUTH_MODE_MISMATCH",
357
+ message: "provider quote spendAuthorizationMode mismatch"
358
+ };
359
+ }
360
+ if (Date.parse(String(normalizedQuote.expiresAt ?? "")) <= Date.now()) {
361
+ return {
362
+ ok: false,
363
+ code: "X402_PROVIDER_QUOTE_EXPIRED",
364
+ message: "provider quote expired"
365
+ };
366
+ }
367
+ return {
368
+ ok: true,
369
+ quote: normalizedQuote,
370
+ signature: {
371
+ schemaVersion: String(signature.schemaVersion ?? ""),
372
+ keyId: String(signature.keyId ?? ""),
373
+ signedAt: String(signature.signedAt ?? ""),
374
+ nonce: String(signature.nonce ?? ""),
375
+ payloadHash: String(signature.payloadHash ?? ""),
376
+ signatureBase64: String(signature.signatureBase64 ?? "")
377
+ },
378
+ key: providerKey
379
+ };
380
+ }
381
+
382
+ async function readBodyWithLimit(res, { maxBytes }) {
383
+ if (!res?.body) return { ok: true, bytes: 0, buf: Buffer.alloc(0) };
384
+ const stream = Readable.fromWeb(res.body);
385
+ const chunks = [];
386
+ let total = 0;
387
+ for await (const chunk of stream) {
388
+ const b = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
389
+ total += b.length;
390
+ if (total > maxBytes) return { ok: false, error: "too_large", bytes: total };
391
+ chunks.push(b);
392
+ }
393
+ return { ok: true, bytes: total, buf: Buffer.concat(chunks) };
394
+ }
395
+
396
+ const SETTLD_API_URL = new URL(readRequiredEnv("SETTLD_API_URL"));
397
+ const SETTLD_API_KEY = readRequiredEnv("SETTLD_API_KEY");
398
+ const UPSTREAM_URL = new URL(readRequiredEnv("UPSTREAM_URL"));
399
+ const PORT = readOptionalIntEnv("PORT", 8402);
400
+ const HOLDBACK_BPS = readOptionalIntEnv("HOLDBACK_BPS", 0);
401
+ const DISPUTE_WINDOW_MS = readOptionalIntEnv("DISPUTE_WINDOW_MS", 3_600_000);
402
+ const X402_AUTOFUND = readOptionalBoolEnv("X402_AUTOFUND", false);
403
+ const BIND_HOST = readOptionalStringEnv("BIND_HOST", null);
404
+ const X402_PROVIDER_PUBLIC_KEY_PEM = readOptionalStringEnv("X402_PROVIDER_PUBLIC_KEY_PEM", null);
405
+ const X402_PROVIDER_JWKS_URL = readOptionalStringEnv("X402_PROVIDER_JWKS_URL", null);
406
+ const X402_PROVIDER_KEYSET_DEFAULT_MAX_AGE_MS = readOptionalIntEnv("X402_PROVIDER_KEYSET_DEFAULT_MAX_AGE_MS", 300_000);
407
+ const X402_PROVIDER_KEYSET_FETCH_TIMEOUT_MS = readOptionalIntEnv("X402_PROVIDER_KEYSET_FETCH_TIMEOUT_MS", 3_000);
408
+ const providerKeyResolver = createProviderKeyResolver({
409
+ providerPublicKeyPem: X402_PROVIDER_PUBLIC_KEY_PEM,
410
+ providerJwksUrl: X402_PROVIDER_JWKS_URL,
411
+ defaultMaxAgeMs: X402_PROVIDER_KEYSET_DEFAULT_MAX_AGE_MS,
412
+ fetchTimeoutMs: X402_PROVIDER_KEYSET_FETCH_TIMEOUT_MS
413
+ });
414
+
415
+ if (HOLDBACK_BPS < 0 || HOLDBACK_BPS > 10_000) throw new Error("HOLDBACK_BPS must be within 0..10000");
416
+ if (DISPUTE_WINDOW_MS < 0) throw new Error("DISPUTE_WINDOW_MS must be >= 0");
417
+
418
+ const SETTLD_PROTOCOL = "1.0";
419
+ const DEFAULT_TENANT_ID = "tenant_default";
420
+
421
+ function tenantIdForRequest(req) {
422
+ const raw = req?.headers?.["x-proxy-tenant-id"];
423
+ const t = String(raw ?? "").trim();
424
+ return t || DEFAULT_TENANT_ID;
425
+ }
426
+
427
+ function derivePayerAgentId() {
428
+ const keyId = String(SETTLD_API_KEY.split(".")[0] ?? "").trim();
429
+ return `agt_x402_payer_${sanitizeIdSegment(keyId || "api_key")}`;
430
+ }
431
+
432
+ function derivePayeeAgentId() {
433
+ const host = UPSTREAM_URL.host || UPSTREAM_URL.hostname || "upstream";
434
+ return `agt_x402_payee_${sanitizeIdSegment(host)}`;
435
+ }
436
+
437
+ async function settldJson(path, { tenantId, method, idempotencyKey = null, body } = {}) {
438
+ const res = await fetch(new URL(path, SETTLD_API_URL), {
439
+ method: method ?? "POST",
440
+ headers: {
441
+ authorization: `Bearer ${SETTLD_API_KEY}`,
442
+ "x-proxy-tenant-id": String(tenantId ?? DEFAULT_TENANT_ID),
443
+ "x-settld-protocol": SETTLD_PROTOCOL,
444
+ ...(idempotencyKey ? { "x-idempotency-key": idempotencyKey } : {}),
445
+ "content-type": "application/json; charset=utf-8"
446
+ },
447
+ body: JSON.stringify(body ?? {})
448
+ });
449
+ const text = await res.text();
450
+ let json = null;
451
+ try {
452
+ json = text ? JSON.parse(text) : null;
453
+ } catch {}
454
+ if (!res.ok) {
455
+ const msg = json?.message ?? json?.error ?? text ?? `HTTP ${res.status}`;
456
+ const err = new Error(`Settld ${method ?? "POST"} ${path} failed: ${msg}`);
457
+ err.status = res.status;
458
+ err.body = json;
459
+ throw err;
460
+ }
461
+ return json;
462
+ }
463
+
464
+ function readDecisionRecordFromVerify(gateVerify) {
465
+ if (!gateVerify || typeof gateVerify !== "object" || Array.isArray(gateVerify)) return null;
466
+ const direct =
467
+ gateVerify.decisionRecord && typeof gateVerify.decisionRecord === "object" && !Array.isArray(gateVerify.decisionRecord)
468
+ ? gateVerify.decisionRecord
469
+ : null;
470
+ if (direct) return direct;
471
+ const fromTrace =
472
+ gateVerify?.settlement?.decisionTrace?.decisionRecord &&
473
+ typeof gateVerify.settlement.decisionTrace.decisionRecord === "object" &&
474
+ !Array.isArray(gateVerify.settlement.decisionTrace.decisionRecord)
475
+ ? gateVerify.settlement.decisionTrace.decisionRecord
476
+ : null;
477
+ return fromTrace;
478
+ }
479
+
480
+ function collectReasonCodesFromVerify(gateVerify) {
481
+ const fromGateDecision = Array.isArray(gateVerify?.gate?.decision?.reasonCodes) ? gateVerify.gate.decision.reasonCodes : [];
482
+ const fromDecision = Array.isArray(gateVerify?.decision?.reasonCodes) ? gateVerify.decision.reasonCodes : [];
483
+ return normalizePolicyDecisionReasonCodes([...fromGateDecision, ...fromDecision], "gateVerify.reasonCodes");
484
+ }
485
+
486
+ function derivePolicyDecisionFromVerify(gateVerify) {
487
+ const settlementStatus = String(gateVerify?.settlement?.status ?? "").trim().toLowerCase();
488
+ if (settlementStatus === "released") return "allow";
489
+ if (settlementStatus === "refunded") return "deny";
490
+ if (gateVerify?.decision?.shouldAutoResolve === false) return "challenge";
491
+ if (settlementStatus) return "escalate";
492
+ return null;
493
+ }
494
+
495
+ function applySettldDecisionHeaders(outHeaders, { gateId, gateVerify } = {}) {
496
+ if (gateId) outHeaders["x-settld-gate-id"] = gateId;
497
+ if (!gateVerify || typeof gateVerify !== "object" || Array.isArray(gateVerify)) return;
498
+
499
+ outHeaders["x-settld-settlement-status"] = String(gateVerify?.settlement?.status ?? "");
500
+ outHeaders["x-settld-released-amount-cents"] = String(gateVerify?.settlement?.releasedAmountCents ?? "");
501
+ outHeaders["x-settld-refunded-amount-cents"] = String(gateVerify?.settlement?.refundedAmountCents ?? "");
502
+
503
+ const verificationStatus = String(gateVerify?.gate?.decision?.verificationStatus ?? "").trim();
504
+ if (verificationStatus) outHeaders["x-settld-verification-status"] = verificationStatus;
505
+
506
+ const policyDecision = derivePolicyDecisionFromVerify(gateVerify);
507
+ if (policyDecision) outHeaders["x-settld-policy-decision"] = policyDecision;
508
+
509
+ const reasonCodes = collectReasonCodesFromVerify(gateVerify);
510
+ if (reasonCodes.length > 0) {
511
+ outHeaders["x-settld-verification-codes"] = reasonCodes.join(",");
512
+ outHeaders["x-settld-reason-code"] = reasonCodes[0];
513
+ } else if (policyDecision) {
514
+ const fallbackReasonCode =
515
+ policyDecision === "allow"
516
+ ? "POLICY_ALLOW"
517
+ : policyDecision === "deny"
518
+ ? "POLICY_DENY"
519
+ : policyDecision === "challenge"
520
+ ? "POLICY_CHALLENGE"
521
+ : "POLICY_ESCALATE";
522
+ outHeaders["x-settld-reason-code"] = fallbackReasonCode;
523
+ }
524
+
525
+ const decisionRecord = readDecisionRecordFromVerify(gateVerify);
526
+ const decisionId = typeof decisionRecord?.decisionId === "string" ? decisionRecord.decisionId.trim() : "";
527
+ if (decisionId) outHeaders["x-settld-decision-id"] = decisionId;
528
+
529
+ const policyHashRaw =
530
+ typeof decisionRecord?.policyHashUsed === "string" && decisionRecord.policyHashUsed.trim() !== ""
531
+ ? decisionRecord.policyHashUsed
532
+ : typeof decisionRecord?.policyRef?.policyHash === "string" && decisionRecord.policyRef.policyHash.trim() !== ""
533
+ ? decisionRecord.policyRef.policyHash
534
+ : null;
535
+ const policyHash = typeof policyHashRaw === "string" ? policyHashRaw.trim().toLowerCase() : "";
536
+ if (/^[0-9a-f]{64}$/.test(policyHash)) outHeaders["x-settld-policy-hash"] = policyHash;
537
+
538
+ const policyVersion = Number(decisionRecord?.bindings?.policyDecisionFingerprint?.policyVersion ?? Number.NaN);
539
+ if (Number.isSafeInteger(policyVersion) && policyVersion > 0) {
540
+ outHeaders["x-settld-policy-version"] = String(policyVersion);
541
+ }
542
+ const policyDecisionFingerprint =
543
+ decisionRecord?.bindings?.policyDecisionFingerprint &&
544
+ typeof decisionRecord.bindings.policyDecisionFingerprint === "object" &&
545
+ !Array.isArray(decisionRecord.bindings.policyDecisionFingerprint)
546
+ ? decisionRecord.bindings.policyDecisionFingerprint
547
+ : null;
548
+ const policyVerificationMethodHash =
549
+ typeof policyDecisionFingerprint?.verificationMethodHash === "string"
550
+ ? policyDecisionFingerprint.verificationMethodHash.trim().toLowerCase()
551
+ : "";
552
+ if (/^[0-9a-f]{64}$/.test(policyVerificationMethodHash)) {
553
+ outHeaders["x-settld-policy-verification-method-hash"] = policyVerificationMethodHash;
554
+ }
555
+ const policyEvaluationHash =
556
+ typeof policyDecisionFingerprint?.evaluationHash === "string" ? policyDecisionFingerprint.evaluationHash.trim().toLowerCase() : "";
557
+ if (/^[0-9a-f]{64}$/.test(policyEvaluationHash)) {
558
+ outHeaders["x-settld-policy-evaluation-hash"] = policyEvaluationHash;
559
+ }
560
+
561
+ if (gateVerify?.gate?.holdback?.status) outHeaders["x-settld-holdback-status"] = String(gateVerify.gate.holdback.status);
562
+ if (gateVerify?.gate?.holdback?.amountCents !== undefined) outHeaders["x-settld-holdback-amount-cents"] = String(gateVerify.gate.holdback.amountCents);
563
+ }
564
+
565
+ async function handleProxy(req, res) {
566
+ const url = new URL(req.url ?? "/", "http://localhost");
567
+ if (req.method === "GET" && url.pathname === "/healthz") {
568
+ res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
569
+ res.end(JSON.stringify({ ok: true }));
570
+ return;
571
+ }
572
+
573
+ const tenantId = tenantIdForRequest(req);
574
+ const upstreamUrl = new URL(url.pathname + url.search, UPSTREAM_URL);
575
+ const parsedAgentPassportHeader = parseAgentPassportHeader(req.headers);
576
+ if (!parsedAgentPassportHeader.ok) {
577
+ res.writeHead(400, { "content-type": "application/json; charset=utf-8" });
578
+ res.end(
579
+ JSON.stringify({
580
+ ok: false,
581
+ error: "invalid_agent_passport_header",
582
+ message: parsedAgentPassportHeader.message
583
+ })
584
+ );
585
+ return;
586
+ }
587
+ const requestAgentPassport = parsedAgentPassportHeader.agentPassport;
588
+ const headers = new Headers();
589
+ for (const [k, v] of Object.entries(req.headers)) {
590
+ if (v === undefined) continue;
591
+ if (k.toLowerCase() === "host") continue;
592
+ if (k.toLowerCase() === "x-settld-agent-passport") continue;
593
+ if (Array.isArray(v)) headers.set(k, v.join(","));
594
+ else headers.set(k, String(v));
595
+ }
596
+ const gateId = req.headers["x-settld-gate-id"] ? String(req.headers["x-settld-gate-id"]).trim() : null;
597
+ let providerQuoteVerification = null;
598
+
599
+ const ac = new AbortController();
600
+ req.on("close", () => ac.abort());
601
+
602
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
603
+ let upstreamRes = await fetch(upstreamUrl, {
604
+ method: req.method,
605
+ headers,
606
+ body: hasBody ? req : undefined,
607
+ duplex: hasBody ? "half" : undefined,
608
+ redirect: "manual",
609
+ signal: ac.signal
610
+ });
611
+
612
+ // If upstream requests payment, create a Settld gate and return the 402 to the client.
613
+ if (upstreamRes.status === 402) {
614
+ if (gateId) {
615
+ if (hasBody) {
616
+ res.writeHead(502, { "content-type": "application/json; charset=utf-8", "x-settld-gate-id": gateId });
617
+ res.end(JSON.stringify({ ok: false, error: "gateway_retry_requires_buffered_body", gateId }));
618
+ return;
619
+ }
620
+ const paymentRequiredHeaders = Object.fromEntries(upstreamRes.headers.entries());
621
+ const parsedOffer = parseX402PaymentRequired(paymentRequiredHeaders);
622
+ const offerFields = parsedOffer.ok && parsedOffer.fields && typeof parsedOffer.fields === "object" ? parsedOffer.fields : {};
623
+ const requestBindingMode = normalizeStrictRequestBindingMode(offerFields.requestBindingMode);
624
+ const quoteRequired = normalizeOfferBool(offerFields.quoteRequired, { fallback: false });
625
+ const offerQuoteId = normalizeOfferRef(offerFields.quoteId);
626
+ const offerProviderId = normalizeOfferRef(offerFields.providerId);
627
+ const offerToolId = normalizeOfferRef(offerFields.toolId);
628
+ const parsedAmount = extractAmountAndCurrency(offerFields);
629
+ if (!parsedAmount.ok) {
630
+ res.writeHead(502, { "content-type": "application/json; charset=utf-8", "x-settld-gate-id": gateId });
631
+ res.end(JSON.stringify({ ok: false, error: "gateway_offer_invalid", gateId, reason: parsedAmount.error }));
632
+ return;
633
+ }
634
+ let requestBindingSha256 = null;
635
+ if (requestBindingMode === "strict") {
636
+ requestBindingSha256 = computeStrictRequestBindingSha256ForRetry({ reqMethod: req.method, upstreamUrl });
637
+ }
638
+ const quoteHeaders = parseProviderQuoteHeaders(paymentRequiredHeaders);
639
+ const quoteVerified = await verifyProviderQuoteChallenge({
640
+ offerFields,
641
+ amountCents: parsedAmount.amountCents,
642
+ currency: parsedAmount.currency,
643
+ requestBindingMode,
644
+ requestBindingSha256,
645
+ providerKeyResolver,
646
+ quoteHeaders
647
+ });
648
+ if (!quoteVerified.ok) {
649
+ res.writeHead(502, { "content-type": "application/json; charset=utf-8", "x-settld-gate-id": gateId });
650
+ res.end(
651
+ JSON.stringify({
652
+ ok: false,
653
+ error: "gateway_provider_quote_verification_failed",
654
+ gateId,
655
+ code: quoteVerified.code,
656
+ message: quoteVerified.message
657
+ })
658
+ );
659
+ return;
660
+ }
661
+ const verifiedQuote = quoteVerified.quote;
662
+ providerQuoteVerification = quoteVerified.ok
663
+ ? {
664
+ required: providerKeyResolver.enabled,
665
+ verified: true,
666
+ quote: verifiedQuote,
667
+ signature: quoteVerified.signature ?? null,
668
+ key: quoteVerified.key ?? null
669
+ }
670
+ : null;
671
+ let quoted = null;
672
+ const challengeQuoteId = verifiedQuote?.quoteId ? String(verifiedQuote.quoteId) : offerQuoteId ?? null;
673
+ const shouldFetchQuote = quoteRequired || requestBindingMode === "strict" || Boolean(challengeQuoteId);
674
+ if (shouldFetchQuote) {
675
+ quoted = await settldJson("/x402/gate/quote", {
676
+ tenantId,
677
+ method: "POST",
678
+ idempotencyKey: stableIdemKey(
679
+ "x402_quote",
680
+ `${gateId}\n${requestBindingMode ?? "none"}\n${requestBindingSha256 ?? ""}\n${challengeQuoteId ?? ""}`
681
+ ),
682
+ body: {
683
+ gateId,
684
+ ...(requestBindingMode === "strict"
685
+ ? {
686
+ requestBindingMode: "strict",
687
+ requestBindingSha256
688
+ }
689
+ : {}),
690
+ ...(offerProviderId ? { providerId: offerProviderId } : {}),
691
+ ...(offerToolId ? { toolId: offerToolId } : {}),
692
+ ...(challengeQuoteId ? { quoteId: challengeQuoteId } : {})
693
+ }
694
+ });
695
+ }
696
+ const authz = await settldJson("/x402/gate/authorize-payment", {
697
+ tenantId,
698
+ method: "POST",
699
+ idempotencyKey: stableIdemKey(
700
+ "x402_authz",
701
+ `${gateId}\n${requestBindingMode ?? "none"}\n${requestBindingSha256 ?? ""}\n${
702
+ quoted?.quote?.quoteId ?? verifiedQuote?.quoteId ?? offerQuoteId ?? ""
703
+ }`
704
+ ),
705
+ body: {
706
+ gateId,
707
+ ...(requestBindingMode === "strict"
708
+ ? {
709
+ requestBindingMode: "strict",
710
+ requestBindingSha256
711
+ }
712
+ : {}),
713
+ ...(quoted?.quote?.quoteId
714
+ ? { quoteId: String(quoted.quote.quoteId) }
715
+ : challengeQuoteId
716
+ ? { quoteId: challengeQuoteId }
717
+ : {})
718
+ }
719
+ });
720
+ const token = typeof authz?.token === "string" ? authz.token.trim() : "";
721
+ if (!token) {
722
+ res.writeHead(502, { "content-type": "application/json; charset=utf-8", "x-settld-gate-id": gateId });
723
+ res.end(JSON.stringify({ ok: false, error: "gateway_authorization_token_missing", gateId }));
724
+ return;
725
+ }
726
+ headers.set("authorization", `SettldPay ${token}`);
727
+ // Back-compat with the local upstream mock; provider wrappers can rely on Authorization only.
728
+ headers.set("x-payment", token);
729
+ if (typeof authz?.authorizationRef === "string" && authz.authorizationRef.trim() !== "") {
730
+ headers.set("x-settld-authorization-ref", authz.authorizationRef.trim());
731
+ }
732
+ if (typeof authz?.quoteId === "string" && authz.quoteId.trim() !== "") {
733
+ headers.set("x-settld-quote-id", authz.quoteId.trim());
734
+ } else if (quoted?.quote?.quoteId) {
735
+ headers.set("x-settld-quote-id", String(quoted.quote.quoteId));
736
+ }
737
+ upstreamRes = await fetch(upstreamUrl, {
738
+ method: req.method,
739
+ headers,
740
+ body: undefined,
741
+ redirect: "manual",
742
+ signal: ac.signal
743
+ });
744
+ }
745
+
746
+ if (upstreamRes.status === 402 && gateId) {
747
+ const outHeaders = Object.fromEntries(upstreamRes.headers.entries());
748
+ outHeaders["x-settld-gate-id"] = gateId;
749
+ res.writeHead(402, outHeaders);
750
+ res.end(await upstreamRes.text());
751
+ return;
752
+ }
753
+
754
+ if (!gateId) {
755
+ const headersObj = Object.fromEntries(upstreamRes.headers.entries());
756
+ const parsed = parseX402PaymentRequired(headersObj);
757
+ if (!parsed.ok) {
758
+ res.writeHead(402, Object.fromEntries(upstreamRes.headers.entries()));
759
+ res.end(await upstreamRes.text());
760
+ return;
761
+ }
762
+ const amount = extractAmountAndCurrency(parsed.fields);
763
+ if (!amount.ok) {
764
+ res.writeHead(402, Object.fromEntries(upstreamRes.headers.entries()));
765
+ res.end(await upstreamRes.text());
766
+ return;
767
+ }
768
+ const offeredToolId = normalizeOfferRef(parsed.fields?.toolId);
769
+
770
+ const payerAgentId = derivePayerAgentId();
771
+ const payeeAgentId = derivePayeeAgentId();
772
+ const gateCreate = await settldJson("/x402/gate/create", {
773
+ tenantId,
774
+ method: "POST",
775
+ idempotencyKey: stableIdemKey("x402_create", `${upstreamUrl.toString()}\n${parsed.raw}\n${payerAgentId}\n${payeeAgentId}`),
776
+ body: {
777
+ payerAgentId,
778
+ payeeAgentId,
779
+ amountCents: amount.amountCents,
780
+ currency: amount.currency,
781
+ // Local-demo-only: lets the gate create an escrow hold without integrating a real payment rail.
782
+ ...(X402_AUTOFUND ? { autoFundPayerCents: amount.amountCents } : {}),
783
+ holdbackBps: HOLDBACK_BPS,
784
+ disputeWindowMs: DISPUTE_WINDOW_MS,
785
+ ...(offeredToolId ? { toolId: offeredToolId } : {}),
786
+ ...(requestAgentPassport ? { agentPassport: requestAgentPassport } : {}),
787
+ ...(X402_PROVIDER_PUBLIC_KEY_PEM ? { providerPublicKeyPem: X402_PROVIDER_PUBLIC_KEY_PEM } : {}),
788
+ paymentRequiredHeader: { "x-payment-required": parsed.raw }
789
+ }
790
+ });
791
+
792
+ const outHeaders = Object.fromEntries(upstreamRes.headers.entries());
793
+ outHeaders["x-settld-gate-id"] = String(gateCreate?.gate?.gateId ?? "");
794
+ res.writeHead(402, outHeaders);
795
+ res.end(await upstreamRes.text());
796
+ return;
797
+ }
798
+ }
799
+ if (!gateId) {
800
+ res.writeHead(upstreamRes.status, Object.fromEntries(upstreamRes.headers.entries()));
801
+ if (upstreamRes.body) {
802
+ Readable.fromWeb(upstreamRes.body).pipe(res);
803
+ } else {
804
+ res.end();
805
+ }
806
+ return;
807
+ }
808
+
809
+ const requestSha256ForEvidence = computeStrictRequestBindingSha256ForRetry({ reqMethod: req.method, upstreamUrl });
810
+
811
+ try {
812
+ // For "paid" requests, capture a small deterministic response hash and verify before returning.
813
+ const capture = await readBodyWithLimit(upstreamRes, { maxBytes: 2 * 1024 * 1024 });
814
+ if (!capture.ok) {
815
+ const gateVerify = await settldJson("/x402/gate/verify", {
816
+ tenantId,
817
+ method: "POST",
818
+ idempotencyKey: stableIdemKey("x402_verify", `${gateId}\nUNVERIFIABLE\n${upstreamRes.status}`),
819
+ body: {
820
+ gateId,
821
+ verificationStatus: "red",
822
+ runStatus: "failed",
823
+ policy: {
824
+ mode: "automatic",
825
+ rules: {
826
+ autoReleaseOnGreen: true,
827
+ greenReleaseRatePct: 100,
828
+ autoReleaseOnAmber: false,
829
+ amberReleaseRatePct: 0,
830
+ autoReleaseOnRed: true,
831
+ redReleaseRatePct: 0
832
+ }
833
+ },
834
+ verificationMethod: { mode: "deterministic", source: "gateway_unverifiable_v1", attestor: null },
835
+ verificationCodes: ["X402_GATEWAY_RESPONSE_TOO_LARGE"],
836
+ evidenceRefs: [`http:request_sha256:${requestSha256ForEvidence}`, `http:status:${upstreamRes.status}`]
837
+ }
838
+ });
839
+
840
+ const outHeaders = Object.fromEntries(upstreamRes.headers.entries());
841
+ applySettldDecisionHeaders(outHeaders, { gateId, gateVerify });
842
+
843
+ res.writeHead(502, outHeaders);
844
+ res.end(`gateway: response too large to verify (>${2 * 1024 * 1024} bytes); refunded`);
845
+ return;
846
+ }
847
+ const contentType = String(upstreamRes.headers.get("content-type") ?? "");
848
+ const respHash = (() => {
849
+ // If upstream returns JSON, hash canonical JSON instead of raw bytes to avoid whitespace/ordering drift.
850
+ if (contentType.toLowerCase().includes("application/json")) {
851
+ try {
852
+ const parsed = JSON.parse(capture.buf.toString("utf8"));
853
+ return sha256Hex(canonicalJsonStringify(parsed));
854
+ } catch {}
855
+ }
856
+ return sha256Hex(capture.buf);
857
+ })();
858
+
859
+ const providerReasonCodes = [];
860
+ let providerSignature = null;
861
+ let providerSignaturePublicKeyPem = null;
862
+ if (providerKeyResolver.enabled) {
863
+ const keyId = upstreamRes.headers.get("x-settld-provider-key-id");
864
+ const signedAt = upstreamRes.headers.get("x-settld-provider-signed-at");
865
+ const nonce = upstreamRes.headers.get("x-settld-provider-nonce");
866
+ const signedResponseHash = upstreamRes.headers.get("x-settld-provider-response-sha256");
867
+ const signatureBase64 = upstreamRes.headers.get("x-settld-provider-signature");
868
+
869
+ if (!keyId || !signedAt || !nonce || !signedResponseHash || !signatureBase64) {
870
+ providerReasonCodes.push("X402_PROVIDER_SIGNATURE_MISSING");
871
+ } else if (String(signedResponseHash).trim().toLowerCase() !== respHash) {
872
+ providerReasonCodes.push("X402_PROVIDER_RESPONSE_HASH_MISMATCH");
873
+ } else {
874
+ try {
875
+ const normalizedKeyId = String(keyId).trim();
876
+ const resolvedProviderKey = await providerKeyResolver.resolveByKeyId(normalizedKeyId);
877
+ if (!resolvedProviderKey?.publicKeyPem) {
878
+ providerReasonCodes.push("X402_PROVIDER_KEY_ID_UNKNOWN");
879
+ throw new Error("provider key id unknown");
880
+ }
881
+ providerSignaturePublicKeyPem = resolvedProviderKey.publicKeyPem;
882
+ const payloadHash = computeToolProviderSignaturePayloadHashV1({ responseHash: respHash, nonce, signedAt });
883
+ providerSignature = {
884
+ schemaVersion: "ToolProviderSignature.v1",
885
+ algorithm: "ed25519",
886
+ keyId: normalizedKeyId,
887
+ signedAt: String(signedAt).trim(),
888
+ nonce: String(nonce).trim(),
889
+ responseHash: respHash,
890
+ payloadHash,
891
+ signatureBase64: String(signatureBase64).trim()
892
+ };
893
+ let ok = false;
894
+ try {
895
+ ok = verifyToolProviderSignatureV1({ signature: providerSignature, publicKeyPem: providerSignaturePublicKeyPem });
896
+ } catch {
897
+ ok = false;
898
+ }
899
+ if (!ok) providerReasonCodes.push("X402_PROVIDER_SIGNATURE_INVALID");
900
+ } catch {
901
+ if (!providerReasonCodes.includes("X402_PROVIDER_KEY_ID_UNKNOWN")) {
902
+ providerReasonCodes.push("X402_PROVIDER_SIGNATURE_INVALID");
903
+ }
904
+ }
905
+ }
906
+ }
907
+
908
+ // Deterministic default: release 100% on PASS; refund 100% on FAIL.
909
+ const policy = {
910
+ mode: "automatic",
911
+ rules: {
912
+ autoReleaseOnGreen: true,
913
+ greenReleaseRatePct: 100,
914
+ autoReleaseOnAmber: false,
915
+ amberReleaseRatePct: 0,
916
+ autoReleaseOnRed: true,
917
+ redReleaseRatePct: 0
918
+ }
919
+ };
920
+
921
+ const gateVerify = await settldJson("/x402/gate/verify", {
922
+ tenantId,
923
+ method: "POST",
924
+ idempotencyKey: stableIdemKey("x402_verify", `${gateId}\n${respHash}`),
925
+ body: {
926
+ gateId,
927
+ verificationStatus:
928
+ upstreamRes.ok && (!providerKeyResolver.enabled || providerReasonCodes.length === 0) ? "green" : "red",
929
+ runStatus: upstreamRes.ok ? "completed" : "failed",
930
+ policy,
931
+ verificationMethod: {
932
+ mode: providerKeyResolver.enabled ? "attested" : "deterministic",
933
+ source: providerKeyResolver.enabled ? "provider_signature_v1" : "http_status_v1",
934
+ attestor: providerSignature?.keyId ?? null
935
+ },
936
+ ...(providerSignature && providerSignaturePublicKeyPem
937
+ ? { providerSignature: { ...providerSignature, publicKeyPem: providerSignaturePublicKeyPem } }
938
+ : {}),
939
+ ...(providerQuoteVerification?.quote && providerQuoteVerification?.signature
940
+ ? {
941
+ providerQuotePayload: providerQuoteVerification.quote,
942
+ providerQuoteSignature: {
943
+ schemaVersion: String(providerQuoteVerification.signature.schemaVersion ?? ""),
944
+ algorithm: String(providerQuoteVerification.signature.algorithm ?? ""),
945
+ keyId: String(providerQuoteVerification.signature.keyId ?? ""),
946
+ signedAt: String(providerQuoteVerification.signature.signedAt ?? ""),
947
+ nonce: String(providerQuoteVerification.signature.nonce ?? ""),
948
+ payloadHash: String(providerQuoteVerification.signature.payloadHash ?? ""),
949
+ signatureBase64: String(providerQuoteVerification.signature.signatureBase64 ?? ""),
950
+ quoteId:
951
+ typeof providerQuoteVerification.quote.quoteId === "string"
952
+ ? providerQuoteVerification.quote.quoteId
953
+ : null,
954
+ quoteSha256: sha256Hex(canonicalJsonStringify(providerQuoteVerification.quote)),
955
+ publicKeyPem:
956
+ typeof providerQuoteVerification.key?.publicKeyPem === "string"
957
+ ? providerQuoteVerification.key.publicKeyPem
958
+ : null
959
+ }
960
+ }
961
+ : {}),
962
+ verificationCodes: providerReasonCodes,
963
+ evidenceRefs: [
964
+ `http:request_sha256:${requestSha256ForEvidence}`,
965
+ `http:response_sha256:${respHash}`,
966
+ `http:status:${upstreamRes.status}`,
967
+ ...(providerQuoteVerification?.quote
968
+ ? [
969
+ `provider_quote:quote_id:${String(providerQuoteVerification.quote.quoteId ?? "")}`,
970
+ `provider_quote:payload_sha256:${sha256Hex(canonicalJsonStringify(providerQuoteVerification.quote))}`
971
+ ]
972
+ : []),
973
+ ...(providerSignature
974
+ ? [
975
+ `provider:key_id:${providerSignature.keyId}`,
976
+ `provider:signed_at:${providerSignature.signedAt}`,
977
+ `provider:nonce:${providerSignature.nonce}`,
978
+ `provider:payload_sha256:${providerSignature.payloadHash}`,
979
+ `provider:sig_b64:${providerSignature.signatureBase64}`
980
+ ]
981
+ : [])
982
+ ]
983
+ }
984
+ });
985
+
986
+ const outHeaders = Object.fromEntries(upstreamRes.headers.entries());
987
+ outHeaders["x-settld-response-sha256"] = respHash;
988
+ applySettldDecisionHeaders(outHeaders, { gateId, gateVerify });
989
+
990
+ res.writeHead(upstreamRes.status, outHeaders);
991
+ res.end(capture.buf);
992
+ } catch (err) {
993
+ // Best-effort: if anything goes wrong after a hold exists, force the gate red to refund instead of stranding escrow.
994
+ let gateVerify = null;
995
+ try {
996
+ gateVerify = await settldJson("/x402/gate/verify", {
997
+ tenantId,
998
+ method: "POST",
999
+ idempotencyKey: stableIdemKey("x402_verify", `${gateId}\nERROR\n${upstreamRes.status}`),
1000
+ body: {
1001
+ gateId,
1002
+ verificationStatus: "red",
1003
+ runStatus: "failed",
1004
+ policy: {
1005
+ mode: "automatic",
1006
+ rules: {
1007
+ autoReleaseOnGreen: true,
1008
+ greenReleaseRatePct: 100,
1009
+ autoReleaseOnAmber: false,
1010
+ amberReleaseRatePct: 0,
1011
+ autoReleaseOnRed: true,
1012
+ redReleaseRatePct: 0
1013
+ }
1014
+ },
1015
+ verificationMethod: { mode: "deterministic", source: "gateway_error_v1", attestor: null },
1016
+ verificationCodes: ["X402_GATEWAY_ERROR"],
1017
+ evidenceRefs: [`http:request_sha256:${requestSha256ForEvidence}`, `http:status:${upstreamRes.status}`]
1018
+ }
1019
+ });
1020
+ } catch {}
1021
+
1022
+ const outHeaders = Object.fromEntries(upstreamRes.headers.entries());
1023
+ if (gateVerify) {
1024
+ applySettldDecisionHeaders(outHeaders, { gateId, gateVerify });
1025
+ } else if (gateId) {
1026
+ outHeaders["x-settld-gate-id"] = gateId;
1027
+ }
1028
+
1029
+ res.writeHead(502, outHeaders);
1030
+ res.end(`gateway error: ${err?.message ?? String(err ?? "")}`);
1031
+ }
1032
+ }
1033
+
1034
+ const server = http.createServer((req, res) => {
1035
+ handleProxy(req, res).catch((err) => {
1036
+ res.statusCode = 502;
1037
+ res.setHeader("content-type", "application/json; charset=utf-8");
1038
+ res.end(JSON.stringify({ ok: false, error: "gateway_error", message: err?.message ?? String(err ?? "") }));
1039
+ });
1040
+ });
1041
+
1042
+ const listenCb = () => {
1043
+ // eslint-disable-next-line no-console
1044
+ console.log(
1045
+ JSON.stringify({
1046
+ ok: true,
1047
+ service: "x402-gateway",
1048
+ ...(BIND_HOST ? { host: BIND_HOST } : {}),
1049
+ port: PORT,
1050
+ upstreamUrl: UPSTREAM_URL.toString(),
1051
+ settldApiUrl: SETTLD_API_URL.toString(),
1052
+ holdbackBps: HOLDBACK_BPS,
1053
+ disputeWindowMs: DISPUTE_WINDOW_MS
1054
+ })
1055
+ );
1056
+ };
1057
+ if (BIND_HOST) server.listen(PORT, BIND_HOST, listenCb);
1058
+ else server.listen(PORT, listenCb);