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
@@ -153,8 +153,34 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
153
153
  store.months.clear();
154
154
  if (!(store.agentRuns instanceof Map)) store.agentRuns = new Map();
155
155
  store.agentRuns.clear();
156
+ if (!(store.agentPassports instanceof Map)) store.agentPassports = new Map();
157
+ store.agentPassports.clear();
156
158
  if (!(store.arbitrationCases instanceof Map)) store.arbitrationCases = new Map();
157
159
  store.arbitrationCases.clear();
160
+ if (!(store.agreementDelegations instanceof Map)) store.agreementDelegations = new Map();
161
+ store.agreementDelegations.clear();
162
+ if (!(store.x402Gates instanceof Map)) store.x402Gates = new Map();
163
+ store.x402Gates.clear();
164
+ if (!(store.x402AgentLifecycles instanceof Map)) store.x402AgentLifecycles = new Map();
165
+ store.x402AgentLifecycles.clear();
166
+ if (!(store.x402Receipts instanceof Map)) store.x402Receipts = new Map();
167
+ store.x402Receipts.clear();
168
+ if (!(store.x402WalletPolicies instanceof Map)) store.x402WalletPolicies = new Map();
169
+ store.x402WalletPolicies.clear();
170
+ if (!(store.x402Escalations instanceof Map)) store.x402Escalations = new Map();
171
+ store.x402Escalations.clear();
172
+ if (!(store.x402EscalationEvents instanceof Map)) store.x402EscalationEvents = new Map();
173
+ store.x402EscalationEvents.clear();
174
+ if (!(store.x402EscalationOverrideUsage instanceof Map)) store.x402EscalationOverrideUsage = new Map();
175
+ store.x402EscalationOverrideUsage.clear();
176
+ if (!(store.x402ZkVerificationKeys instanceof Map)) store.x402ZkVerificationKeys = new Map();
177
+ store.x402ZkVerificationKeys.clear();
178
+ if (!(store.x402ReversalEvents instanceof Map)) store.x402ReversalEvents = new Map();
179
+ store.x402ReversalEvents.clear();
180
+ if (!(store.x402ReversalNonceUsage instanceof Map)) store.x402ReversalNonceUsage = new Map();
181
+ store.x402ReversalNonceUsage.clear();
182
+ if (!(store.x402ReversalCommandUsage instanceof Map)) store.x402ReversalCommandUsage = new Map();
183
+ store.x402ReversalCommandUsage.clear();
158
184
  if (!(store.toolCallHolds instanceof Map)) store.toolCallHolds = new Map();
159
185
  store.toolCallHolds.clear();
160
186
  if (!(store.settlementAdjustments instanceof Map)) store.settlementAdjustments = new Map();
@@ -173,6 +199,17 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
173
199
  if (type === "operator") store.operators.set(key, { ...snap, tenantId: snap?.tenantId ?? tenantId });
174
200
  if (type === "month") store.months.set(key, { ...snap, tenantId: snap?.tenantId ?? tenantId });
175
201
  if (type === "agent_run") store.agentRuns.set(key, { ...snap, tenantId: snap?.tenantId ?? tenantId, runId: snap?.runId ?? String(id) });
202
+ if (type === "agent_passport") {
203
+ const status = typeof snap?.status === "string" ? snap.status.trim().toLowerCase() : "";
204
+ if (status === "active" || status === "suspended" || status === "revoked") {
205
+ store.agentPassports.set(key, {
206
+ ...snap,
207
+ tenantId: snap?.tenantId ?? tenantId,
208
+ agentId: snap?.agentId ?? String(id),
209
+ status
210
+ });
211
+ }
212
+ }
176
213
  if (type === "arbitration_case") {
177
214
  store.arbitrationCases.set(key, {
178
215
  ...snap,
@@ -180,6 +217,105 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
180
217
  caseId: snap?.caseId ?? String(id)
181
218
  });
182
219
  }
220
+ if (type === "agreement_delegation") {
221
+ store.agreementDelegations.set(key, {
222
+ ...snap,
223
+ tenantId: snap?.tenantId ?? tenantId,
224
+ delegationId: snap?.delegationId ?? String(id)
225
+ });
226
+ }
227
+ if (type === "x402_gate") {
228
+ store.x402Gates.set(key, {
229
+ ...snap,
230
+ tenantId: snap?.tenantId ?? tenantId,
231
+ gateId: snap?.gateId ?? String(id)
232
+ });
233
+ }
234
+ if (type === "x402_agent_lifecycle") {
235
+ store.x402AgentLifecycles.set(key, {
236
+ ...snap,
237
+ tenantId: snap?.tenantId ?? tenantId,
238
+ agentId: snap?.agentId ?? String(id)
239
+ });
240
+ }
241
+ if (type === "x402_receipt") {
242
+ store.x402Receipts.set(key, {
243
+ ...snap,
244
+ tenantId: snap?.tenantId ?? tenantId,
245
+ receiptId: snap?.receiptId ?? String(id),
246
+ reversal: null,
247
+ reversalEvents: []
248
+ });
249
+ }
250
+ if (type === "x402_wallet_policy") {
251
+ const sponsorWalletRef = typeof snap?.sponsorWalletRef === "string" ? snap.sponsorWalletRef : null;
252
+ const policyRef = typeof snap?.policyRef === "string" ? snap.policyRef : null;
253
+ const policyVersion = parseSafeIntegerOrNull(snap?.policyVersion);
254
+ if (sponsorWalletRef && policyRef && policyVersion !== null && policyVersion > 0) {
255
+ store.x402WalletPolicies.set(makeScopedKey({ tenantId, id: `${sponsorWalletRef}::${policyRef}::${policyVersion}` }), {
256
+ ...snap,
257
+ tenantId: snap?.tenantId ?? tenantId,
258
+ sponsorWalletRef,
259
+ policyRef,
260
+ policyVersion
261
+ });
262
+ }
263
+ }
264
+ if (type === "x402_escalation") {
265
+ store.x402Escalations.set(key, {
266
+ ...snap,
267
+ tenantId: snap?.tenantId ?? tenantId,
268
+ escalationId: snap?.escalationId ?? String(id)
269
+ });
270
+ }
271
+ if (type === "x402_escalation_event") {
272
+ store.x402EscalationEvents.set(key, {
273
+ ...snap,
274
+ tenantId: snap?.tenantId ?? tenantId,
275
+ eventId: snap?.eventId ?? String(id)
276
+ });
277
+ }
278
+ if (type === "x402_escalation_override_usage") {
279
+ store.x402EscalationOverrideUsage.set(key, {
280
+ ...snap,
281
+ tenantId: snap?.tenantId ?? tenantId,
282
+ overrideId: snap?.overrideId ?? String(id)
283
+ });
284
+ }
285
+ if (type === "x402_zk_verification_key") {
286
+ store.x402ZkVerificationKeys.set(key, {
287
+ ...snap,
288
+ tenantId: snap?.tenantId ?? tenantId,
289
+ verificationKeyId: snap?.verificationKeyId ?? String(id)
290
+ });
291
+ }
292
+ if (type === "x402_reversal_event") {
293
+ store.x402ReversalEvents.set(key, {
294
+ ...snap,
295
+ tenantId: snap?.tenantId ?? tenantId,
296
+ eventId: snap?.eventId ?? String(id)
297
+ });
298
+ }
299
+ if (type === "x402_reversal_nonce") {
300
+ const sponsorRef = typeof snap?.sponsorRef === "string" ? snap.sponsorRef : null;
301
+ const nonce = typeof snap?.nonce === "string" ? snap.nonce : null;
302
+ if (sponsorRef && nonce) {
303
+ const nonceKey = `${tenantId}\n${sponsorRef}\n${nonce}`;
304
+ store.x402ReversalNonceUsage.set(nonceKey, {
305
+ ...snap,
306
+ tenantId,
307
+ sponsorRef,
308
+ nonce
309
+ });
310
+ }
311
+ }
312
+ if (type === "x402_reversal_command") {
313
+ store.x402ReversalCommandUsage.set(key, {
314
+ ...snap,
315
+ tenantId: snap?.tenantId ?? tenantId,
316
+ commandId: snap?.commandId ?? String(id)
317
+ });
318
+ }
183
319
  if (type === "tool_call_hold") {
184
320
  store.toolCallHolds.set(key, {
185
321
  ...snap,
@@ -739,6 +875,26 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
739
875
  };
740
876
  }
741
877
 
878
+ function agentPassportSnapshotRowToRecord(row) {
879
+ const passport = row?.snapshot_json ?? null;
880
+ if (!passport || typeof passport !== "object" || Array.isArray(passport)) return null;
881
+ const tenantId = normalizeTenantId(row?.tenant_id ?? passport?.tenantId ?? DEFAULT_TENANT_ID);
882
+ const agentId = row?.aggregate_id ? String(row.aggregate_id) : passport?.agentId ? String(passport.agentId) : null;
883
+ if (!agentId) return null;
884
+ const status = passport?.status ? String(passport.status).trim().toLowerCase() : "active";
885
+ if (status !== "active" && status !== "suspended" && status !== "revoked") return null;
886
+ const createdAt = parseIsoOrNull(passport?.createdAt) ?? parseIsoOrNull(row?.updated_at) ?? new Date(0).toISOString();
887
+ const updatedAt = parseIsoOrNull(passport?.updatedAt) ?? parseIsoOrNull(row?.updated_at) ?? createdAt;
888
+ return {
889
+ ...passport,
890
+ tenantId,
891
+ agentId,
892
+ status,
893
+ createdAt,
894
+ updatedAt
895
+ };
896
+ }
897
+
742
898
  async function refreshAgentIdentities() {
743
899
  if (!(store.agentIdentities instanceof Map)) store.agentIdentities = new Map();
744
900
  store.agentIdentities.clear();
@@ -1149,6 +1305,494 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
1149
1305
  );
1150
1306
  }
1151
1307
 
1308
+ async function persistAgreementDelegation(client, { tenantId, delegationId, delegation }) {
1309
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1310
+ if (!delegation || typeof delegation !== "object" || Array.isArray(delegation)) {
1311
+ throw new TypeError("delegation is required");
1312
+ }
1313
+ const normalizedDelegationId =
1314
+ delegationId ? String(delegationId) : delegation.delegationId ? String(delegation.delegationId) : null;
1315
+ if (!normalizedDelegationId) throw new TypeError("delegationId is required");
1316
+
1317
+ const updatedAt = parseIsoOrNull(delegation.updatedAt) ?? new Date().toISOString();
1318
+ const normalizedDelegation = {
1319
+ ...delegation,
1320
+ tenantId,
1321
+ delegationId: normalizedDelegationId,
1322
+ updatedAt
1323
+ };
1324
+
1325
+ await client.query(
1326
+ `
1327
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1328
+ VALUES ($1, 'agreement_delegation', $2, 0, NULL, $3, $4)
1329
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1330
+ snapshot_json = EXCLUDED.snapshot_json,
1331
+ updated_at = EXCLUDED.updated_at
1332
+ `,
1333
+ [tenantId, normalizedDelegationId, JSON.stringify(normalizedDelegation), updatedAt]
1334
+ );
1335
+ }
1336
+
1337
+ async function persistX402Gate(client, { tenantId, gateId, gate }) {
1338
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1339
+ if (!gate || typeof gate !== "object" || Array.isArray(gate)) {
1340
+ throw new TypeError("gate is required");
1341
+ }
1342
+ const normalizedGateId = gateId ? String(gateId) : gate.gateId ? String(gate.gateId) : gate.id ? String(gate.id) : null;
1343
+ if (!normalizedGateId) throw new TypeError("gateId is required");
1344
+
1345
+ const updatedAt = parseIsoOrNull(gate.updatedAt) ?? new Date().toISOString();
1346
+ const normalizedGate = {
1347
+ ...gate,
1348
+ tenantId,
1349
+ gateId: normalizedGateId,
1350
+ updatedAt
1351
+ };
1352
+
1353
+ await client.query(
1354
+ `
1355
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1356
+ VALUES ($1, 'x402_gate', $2, 0, NULL, $3, $4)
1357
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1358
+ snapshot_json = EXCLUDED.snapshot_json,
1359
+ updated_at = EXCLUDED.updated_at
1360
+ `,
1361
+ [tenantId, normalizedGateId, JSON.stringify(normalizedGate), updatedAt]
1362
+ );
1363
+ }
1364
+
1365
+ async function persistX402AgentLifecycle(client, { tenantId, agentId, agentLifecycle }) {
1366
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1367
+ if (!agentLifecycle || typeof agentLifecycle !== "object" || Array.isArray(agentLifecycle)) {
1368
+ throw new TypeError("agentLifecycle is required");
1369
+ }
1370
+ const normalizedAgentId =
1371
+ agentId ? String(agentId) : agentLifecycle.agentId ? String(agentLifecycle.agentId) : null;
1372
+ if (!normalizedAgentId) throw new TypeError("agentId is required");
1373
+ const status = String(agentLifecycle.status ?? "").trim().toLowerCase();
1374
+ if (status !== "active" && status !== "frozen" && status !== "archived") {
1375
+ throw new TypeError("agentLifecycle.status must be active|frozen|archived");
1376
+ }
1377
+
1378
+ const updatedAt = parseIsoOrNull(agentLifecycle.updatedAt) ?? new Date().toISOString();
1379
+ const normalizedAgentLifecycle = {
1380
+ ...agentLifecycle,
1381
+ tenantId,
1382
+ agentId: normalizedAgentId,
1383
+ status,
1384
+ updatedAt
1385
+ };
1386
+
1387
+ await client.query(
1388
+ `
1389
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1390
+ VALUES ($1, 'x402_agent_lifecycle', $2, 0, NULL, $3, $4)
1391
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1392
+ snapshot_json = EXCLUDED.snapshot_json,
1393
+ updated_at = EXCLUDED.updated_at
1394
+ `,
1395
+ [tenantId, normalizedAgentId, JSON.stringify(normalizedAgentLifecycle), updatedAt]
1396
+ );
1397
+ }
1398
+
1399
+ async function persistAgentPassport(client, { tenantId, agentId, agentPassport }) {
1400
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1401
+ if (!agentPassport || typeof agentPassport !== "object" || Array.isArray(agentPassport)) {
1402
+ throw new TypeError("agentPassport is required");
1403
+ }
1404
+ const normalizedAgentId =
1405
+ agentId && String(agentId).trim() !== ""
1406
+ ? String(agentId).trim()
1407
+ : agentPassport.agentId && String(agentPassport.agentId).trim() !== ""
1408
+ ? String(agentPassport.agentId).trim()
1409
+ : null;
1410
+ if (!normalizedAgentId) throw new TypeError("agentId is required");
1411
+
1412
+ const status = String(agentPassport.status ?? "").trim().toLowerCase();
1413
+ if (status !== "active" && status !== "suspended" && status !== "revoked") {
1414
+ throw new TypeError("agentPassport.status must be active|suspended|revoked");
1415
+ }
1416
+ const createdAt = parseIsoOrNull(agentPassport.createdAt) ?? new Date().toISOString();
1417
+ const updatedAt = parseIsoOrNull(agentPassport.updatedAt) ?? createdAt;
1418
+ const normalizedAgentPassport = {
1419
+ ...agentPassport,
1420
+ tenantId,
1421
+ agentId: normalizedAgentId,
1422
+ status,
1423
+ createdAt,
1424
+ updatedAt
1425
+ };
1426
+
1427
+ await client.query(
1428
+ `
1429
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1430
+ VALUES ($1, 'agent_passport', $2, 0, NULL, $3, $4)
1431
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1432
+ snapshot_json = EXCLUDED.snapshot_json,
1433
+ updated_at = EXCLUDED.updated_at
1434
+ `,
1435
+ [tenantId, normalizedAgentId, JSON.stringify(normalizedAgentPassport), updatedAt]
1436
+ );
1437
+ }
1438
+
1439
+ async function persistX402Receipt(client, { tenantId, receiptId, receipt }) {
1440
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1441
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
1442
+ throw new TypeError("receipt is required");
1443
+ }
1444
+ const normalizedReceiptId =
1445
+ receiptId ? String(receiptId) : receipt.receiptId ? String(receipt.receiptId) : null;
1446
+ if (!normalizedReceiptId) throw new TypeError("receiptId is required");
1447
+
1448
+ const updatedAt =
1449
+ parseIsoOrNull(receipt.createdAt) ??
1450
+ parseIsoOrNull(receipt.settledAt) ??
1451
+ parseIsoOrNull(receipt.updatedAt) ??
1452
+ new Date().toISOString();
1453
+ const normalizedReceipt = {
1454
+ ...receipt,
1455
+ tenantId,
1456
+ receiptId: normalizedReceiptId,
1457
+ reversal: null,
1458
+ reversalEvents: [],
1459
+ updatedAt
1460
+ };
1461
+
1462
+ const inserted = await client.query(
1463
+ `
1464
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1465
+ VALUES ($1, 'x402_receipt', $2, 0, NULL, $3, $4)
1466
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO NOTHING
1467
+ RETURNING aggregate_id
1468
+ `,
1469
+ [tenantId, normalizedReceiptId, JSON.stringify(normalizedReceipt), updatedAt]
1470
+ );
1471
+ if (inserted.rows.length > 0) return;
1472
+
1473
+ const existing = await client.query(
1474
+ `
1475
+ SELECT snapshot_json
1476
+ FROM snapshots
1477
+ WHERE tenant_id = $1 AND aggregate_type = 'x402_receipt' AND aggregate_id = $2
1478
+ LIMIT 1
1479
+ `,
1480
+ [tenantId, normalizedReceiptId]
1481
+ );
1482
+ if (!existing.rows.length) return;
1483
+
1484
+ const existingCanonical = canonicalJsonStringify(existing.rows[0]?.snapshot_json ?? null);
1485
+ const incomingCanonical = canonicalJsonStringify(normalizedReceipt);
1486
+ if (existingCanonical !== incomingCanonical) {
1487
+ const err = new Error("x402 receipt is immutable and cannot be changed");
1488
+ err.code = "X402_RECEIPT_IMMUTABLE";
1489
+ err.receiptId = normalizedReceiptId;
1490
+ throw err;
1491
+ }
1492
+ }
1493
+
1494
+ async function persistX402WalletPolicy(client, { tenantId, policy }) {
1495
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1496
+ if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
1497
+ throw new TypeError("policy is required");
1498
+ }
1499
+ const sponsorWalletRef =
1500
+ policy.sponsorWalletRef && String(policy.sponsorWalletRef).trim() !== ""
1501
+ ? String(policy.sponsorWalletRef).trim()
1502
+ : null;
1503
+ const policyRef = policy.policyRef && String(policy.policyRef).trim() !== "" ? String(policy.policyRef).trim() : null;
1504
+ const policyVersion = parseSafeIntegerOrNull(policy.policyVersion);
1505
+ if (!sponsorWalletRef) throw new TypeError("policy.sponsorWalletRef is required");
1506
+ if (!policyRef) throw new TypeError("policy.policyRef is required");
1507
+ if (policyVersion === null || policyVersion <= 0) throw new TypeError("policy.policyVersion must be >= 1");
1508
+ const aggregateId = `${sponsorWalletRef}::${policyRef}::${policyVersion}`;
1509
+ const updatedAt = parseIsoOrNull(policy.updatedAt) ?? new Date().toISOString();
1510
+ const normalizedPolicy = {
1511
+ ...policy,
1512
+ tenantId,
1513
+ sponsorWalletRef,
1514
+ policyRef,
1515
+ policyVersion,
1516
+ updatedAt
1517
+ };
1518
+
1519
+ await client.query(
1520
+ `
1521
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1522
+ VALUES ($1, 'x402_wallet_policy', $2, 0, NULL, $3, $4)
1523
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1524
+ snapshot_json = EXCLUDED.snapshot_json,
1525
+ updated_at = EXCLUDED.updated_at
1526
+ `,
1527
+ [tenantId, aggregateId, JSON.stringify(normalizedPolicy), updatedAt]
1528
+ );
1529
+ }
1530
+
1531
+ async function persistX402Escalation(client, { tenantId, escalationId, escalation }) {
1532
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1533
+ if (!escalation || typeof escalation !== "object" || Array.isArray(escalation)) {
1534
+ throw new TypeError("escalation is required");
1535
+ }
1536
+ const normalizedEscalationId =
1537
+ escalationId && String(escalationId).trim() !== ""
1538
+ ? String(escalationId).trim()
1539
+ : escalation.escalationId && String(escalation.escalationId).trim() !== ""
1540
+ ? String(escalation.escalationId).trim()
1541
+ : null;
1542
+ if (!normalizedEscalationId) throw new TypeError("escalationId is required");
1543
+ const updatedAt =
1544
+ parseIsoOrNull(escalation.updatedAt) ??
1545
+ parseIsoOrNull(escalation.resolvedAt) ??
1546
+ parseIsoOrNull(escalation.createdAt) ??
1547
+ new Date().toISOString();
1548
+ const normalizedEscalation = {
1549
+ ...escalation,
1550
+ tenantId,
1551
+ escalationId: normalizedEscalationId,
1552
+ updatedAt
1553
+ };
1554
+
1555
+ await client.query(
1556
+ `
1557
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1558
+ VALUES ($1, 'x402_escalation', $2, 0, NULL, $3, $4)
1559
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1560
+ snapshot_json = EXCLUDED.snapshot_json,
1561
+ updated_at = EXCLUDED.updated_at
1562
+ `,
1563
+ [tenantId, normalizedEscalationId, JSON.stringify(normalizedEscalation), updatedAt]
1564
+ );
1565
+ }
1566
+
1567
+ async function persistX402EscalationEvent(client, { tenantId, eventId, escalationId, event }) {
1568
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1569
+ if (!event || typeof event !== "object" || Array.isArray(event)) {
1570
+ throw new TypeError("event is required");
1571
+ }
1572
+ const normalizedEventId =
1573
+ eventId && String(eventId).trim() !== ""
1574
+ ? String(eventId).trim()
1575
+ : event.eventId && String(event.eventId).trim() !== ""
1576
+ ? String(event.eventId).trim()
1577
+ : event.id && String(event.id).trim() !== ""
1578
+ ? String(event.id).trim()
1579
+ : null;
1580
+ if (!normalizedEventId) throw new TypeError("eventId is required");
1581
+ const normalizedEscalationId =
1582
+ escalationId && String(escalationId).trim() !== ""
1583
+ ? String(escalationId).trim()
1584
+ : event.escalationId && String(event.escalationId).trim() !== ""
1585
+ ? String(event.escalationId).trim()
1586
+ : null;
1587
+ if (!normalizedEscalationId) throw new TypeError("escalationId is required");
1588
+ const updatedAt =
1589
+ parseIsoOrNull(event.occurredAt) ??
1590
+ parseIsoOrNull(event.createdAt) ??
1591
+ new Date().toISOString();
1592
+ const normalizedEvent = {
1593
+ ...event,
1594
+ tenantId,
1595
+ eventId: normalizedEventId,
1596
+ escalationId: normalizedEscalationId,
1597
+ occurredAt: parseIsoOrNull(event.occurredAt) ?? updatedAt
1598
+ };
1599
+
1600
+ await client.query(
1601
+ `
1602
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1603
+ VALUES ($1, 'x402_escalation_event', $2, 0, NULL, $3, $4)
1604
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1605
+ snapshot_json = EXCLUDED.snapshot_json,
1606
+ updated_at = EXCLUDED.updated_at
1607
+ `,
1608
+ [tenantId, normalizedEventId, JSON.stringify(normalizedEvent), updatedAt]
1609
+ );
1610
+ }
1611
+
1612
+ async function persistX402EscalationOverrideUsage(client, { tenantId, overrideId, usage }) {
1613
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1614
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) {
1615
+ throw new TypeError("usage is required");
1616
+ }
1617
+ const normalizedOverrideId =
1618
+ overrideId && String(overrideId).trim() !== ""
1619
+ ? String(overrideId).trim()
1620
+ : usage.overrideId && String(usage.overrideId).trim() !== ""
1621
+ ? String(usage.overrideId).trim()
1622
+ : null;
1623
+ if (!normalizedOverrideId) throw new TypeError("overrideId is required");
1624
+ const updatedAt = parseIsoOrNull(usage.usedAt) ?? new Date().toISOString();
1625
+ const normalizedUsage = {
1626
+ ...usage,
1627
+ tenantId,
1628
+ overrideId: normalizedOverrideId,
1629
+ usedAt: parseIsoOrNull(usage.usedAt) ?? updatedAt
1630
+ };
1631
+
1632
+ await client.query(
1633
+ `
1634
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1635
+ VALUES ($1, 'x402_escalation_override_usage', $2, 0, NULL, $3, $4)
1636
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1637
+ snapshot_json = EXCLUDED.snapshot_json,
1638
+ updated_at = EXCLUDED.updated_at
1639
+ `,
1640
+ [tenantId, normalizedOverrideId, JSON.stringify(normalizedUsage), updatedAt]
1641
+ );
1642
+ }
1643
+
1644
+ async function persistX402ZkVerificationKey(client, { tenantId, verificationKeyId, verificationKey }) {
1645
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1646
+ if (!verificationKey || typeof verificationKey !== "object" || Array.isArray(verificationKey)) {
1647
+ throw new TypeError("verificationKey is required");
1648
+ }
1649
+ const normalizedVerificationKeyId =
1650
+ verificationKeyId && String(verificationKeyId).trim() !== ""
1651
+ ? String(verificationKeyId).trim()
1652
+ : verificationKey.verificationKeyId && String(verificationKey.verificationKeyId).trim() !== ""
1653
+ ? String(verificationKey.verificationKeyId).trim()
1654
+ : null;
1655
+ if (!normalizedVerificationKeyId) throw new TypeError("verificationKeyId is required");
1656
+ const createdAt =
1657
+ parseIsoOrNull(verificationKey.createdAt) ??
1658
+ parseIsoOrNull(verificationKey.updatedAt) ??
1659
+ new Date().toISOString();
1660
+ const normalizedVerificationKey = {
1661
+ ...verificationKey,
1662
+ tenantId,
1663
+ verificationKeyId: normalizedVerificationKeyId,
1664
+ createdAt,
1665
+ updatedAt: createdAt
1666
+ };
1667
+
1668
+ const inserted = await client.query(
1669
+ `
1670
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1671
+ VALUES ($1, 'x402_zk_verification_key', $2, 0, NULL, $3, $4)
1672
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO NOTHING
1673
+ RETURNING aggregate_id
1674
+ `,
1675
+ [tenantId, normalizedVerificationKeyId, JSON.stringify(normalizedVerificationKey), createdAt]
1676
+ );
1677
+ if (inserted.rows.length > 0) return;
1678
+
1679
+ const existing = await client.query(
1680
+ `
1681
+ SELECT snapshot_json
1682
+ FROM snapshots
1683
+ WHERE tenant_id = $1 AND aggregate_type = 'x402_zk_verification_key' AND aggregate_id = $2
1684
+ LIMIT 1
1685
+ `,
1686
+ [tenantId, normalizedVerificationKeyId]
1687
+ );
1688
+ if (!existing.rows.length) return;
1689
+
1690
+ const existingCanonical = canonicalJsonStringify(existing.rows[0]?.snapshot_json ?? null);
1691
+ const incomingCanonical = canonicalJsonStringify(normalizedVerificationKey);
1692
+ if (existingCanonical !== incomingCanonical) {
1693
+ const err = new Error("x402 zk verification key is immutable and cannot be changed");
1694
+ err.code = "X402_ZK_VERIFICATION_KEY_IMMUTABLE";
1695
+ err.verificationKeyId = normalizedVerificationKeyId;
1696
+ throw err;
1697
+ }
1698
+ }
1699
+
1700
+ async function persistX402ReversalEvent(client, { tenantId, gateId, eventId, event }) {
1701
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1702
+ if (!event || typeof event !== "object" || Array.isArray(event)) {
1703
+ throw new TypeError("event is required");
1704
+ }
1705
+ const normalizedEventId = eventId ? String(eventId) : event.eventId ? String(event.eventId) : event.id ? String(event.id) : null;
1706
+ if (!normalizedEventId) throw new TypeError("eventId is required");
1707
+ const normalizedGateId = gateId ? String(gateId) : event.gateId ? String(event.gateId) : null;
1708
+ if (!normalizedGateId) throw new TypeError("gateId is required");
1709
+ const updatedAt = parseIsoOrNull(event.occurredAt ?? event.createdAt) ?? new Date().toISOString();
1710
+ const normalizedEvent = {
1711
+ ...event,
1712
+ tenantId,
1713
+ gateId: normalizedGateId,
1714
+ eventId: normalizedEventId,
1715
+ occurredAt: parseIsoOrNull(event.occurredAt) ?? updatedAt
1716
+ };
1717
+ await client.query(
1718
+ `
1719
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1720
+ VALUES ($1, 'x402_reversal_event', $2, 0, NULL, $3, $4)
1721
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1722
+ snapshot_json = EXCLUDED.snapshot_json,
1723
+ updated_at = EXCLUDED.updated_at
1724
+ `,
1725
+ [tenantId, normalizedEventId, JSON.stringify(normalizedEvent), updatedAt]
1726
+ );
1727
+ }
1728
+
1729
+ async function persistX402ReversalNonceUsage(client, { tenantId, sponsorRef, nonce, usage }) {
1730
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1731
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) throw new TypeError("usage is required");
1732
+ const normalizedSponsorRef =
1733
+ sponsorRef && String(sponsorRef).trim() !== ""
1734
+ ? String(sponsorRef).trim()
1735
+ : usage.sponsorRef && String(usage.sponsorRef).trim() !== ""
1736
+ ? String(usage.sponsorRef).trim()
1737
+ : null;
1738
+ const normalizedNonce =
1739
+ nonce && String(nonce).trim() !== ""
1740
+ ? String(nonce).trim()
1741
+ : usage.nonce && String(usage.nonce).trim() !== ""
1742
+ ? String(usage.nonce).trim()
1743
+ : null;
1744
+ if (!normalizedSponsorRef) throw new TypeError("sponsorRef is required");
1745
+ if (!normalizedNonce) throw new TypeError("nonce is required");
1746
+ const aggregateId = `${normalizedSponsorRef}::${normalizedNonce}`;
1747
+ const updatedAt = parseIsoOrNull(usage.usedAt) ?? new Date().toISOString();
1748
+ const normalizedUsage = {
1749
+ ...usage,
1750
+ tenantId,
1751
+ sponsorRef: normalizedSponsorRef,
1752
+ nonce: normalizedNonce,
1753
+ usedAt: parseIsoOrNull(usage.usedAt) ?? updatedAt
1754
+ };
1755
+ await client.query(
1756
+ `
1757
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1758
+ VALUES ($1, 'x402_reversal_nonce', $2, 0, NULL, $3, $4)
1759
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1760
+ snapshot_json = EXCLUDED.snapshot_json,
1761
+ updated_at = EXCLUDED.updated_at
1762
+ `,
1763
+ [tenantId, aggregateId, JSON.stringify(normalizedUsage), updatedAt]
1764
+ );
1765
+ }
1766
+
1767
+ async function persistX402ReversalCommandUsage(client, { tenantId, commandId, usage }) {
1768
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1769
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) throw new TypeError("usage is required");
1770
+ const normalizedCommandId =
1771
+ commandId && String(commandId).trim() !== ""
1772
+ ? String(commandId).trim()
1773
+ : usage.commandId && String(usage.commandId).trim() !== ""
1774
+ ? String(usage.commandId).trim()
1775
+ : null;
1776
+ if (!normalizedCommandId) throw new TypeError("commandId is required");
1777
+ const updatedAt = parseIsoOrNull(usage.usedAt) ?? new Date().toISOString();
1778
+ const normalizedUsage = {
1779
+ ...usage,
1780
+ tenantId,
1781
+ commandId: normalizedCommandId,
1782
+ usedAt: parseIsoOrNull(usage.usedAt) ?? updatedAt
1783
+ };
1784
+ await client.query(
1785
+ `
1786
+ INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
1787
+ VALUES ($1, 'x402_reversal_command', $2, 0, NULL, $3, $4)
1788
+ ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
1789
+ snapshot_json = EXCLUDED.snapshot_json,
1790
+ updated_at = EXCLUDED.updated_at
1791
+ `,
1792
+ [tenantId, normalizedCommandId, JSON.stringify(normalizedUsage), updatedAt]
1793
+ );
1794
+ }
1795
+
1152
1796
  async function persistToolCallHold(client, { tenantId, holdHash, hold }) {
1153
1797
  tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
1154
1798
  if (!hold || typeof hold !== "object" || Array.isArray(hold)) {
@@ -1639,10 +2283,23 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
1639
2283
  "SIGNER_KEY_UPSERT",
1640
2284
  "SIGNER_KEY_STATUS_SET",
1641
2285
  "AGENT_IDENTITY_UPSERT",
2286
+ "AGENT_PASSPORT_UPSERT",
1642
2287
  "AGENT_WALLET_UPSERT",
1643
2288
  "AGENT_RUN_EVENTS_APPENDED",
1644
2289
  "AGENT_RUN_SETTLEMENT_UPSERT",
1645
2290
  "ARBITRATION_CASE_UPSERT",
2291
+ "AGREEMENT_DELEGATION_UPSERT",
2292
+ "X402_GATE_UPSERT",
2293
+ "X402_AGENT_LIFECYCLE_UPSERT",
2294
+ "X402_RECEIPT_PUT",
2295
+ "X402_WALLET_POLICY_UPSERT",
2296
+ "X402_ESCALATION_UPSERT",
2297
+ "X402_ESCALATION_EVENT_APPEND",
2298
+ "X402_ESCALATION_OVERRIDE_USAGE_PUT",
2299
+ "X402_ZK_VERIFICATION_KEY_PUT",
2300
+ "X402_REVERSAL_EVENT_APPEND",
2301
+ "X402_REVERSAL_NONCE_PUT",
2302
+ "X402_REVERSAL_COMMAND_PUT",
1646
2303
  "TOOL_CALL_HOLD_UPSERT",
1647
2304
  "SETTLEMENT_ADJUSTMENT_PUT",
1648
2305
  "MARKETPLACE_RFQ_UPSERT",
@@ -2028,6 +2685,45 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
2028
2685
  );
2029
2686
  }
2030
2687
 
2688
+ async function persistReputationEventIndexRow(client, { tenantId, artifactId, artifactHash, artifact }) {
2689
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
2690
+ if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) return;
2691
+ if (String(artifact.artifactType ?? artifact.schemaVersion ?? "") !== "ReputationEvent.v1") return;
2692
+
2693
+ const subject = artifact.subject && typeof artifact.subject === "object" && !Array.isArray(artifact.subject) ? artifact.subject : null;
2694
+ const sourceRef = artifact.sourceRef && typeof artifact.sourceRef === "object" && !Array.isArray(artifact.sourceRef) ? artifact.sourceRef : null;
2695
+ const agentId = subject?.agentId ? String(subject.agentId).trim() : "";
2696
+ if (!agentId) return;
2697
+
2698
+ const toolIdRaw = subject?.toolId ? String(subject.toolId).trim() : "";
2699
+ const toolId = toolIdRaw === "" ? null : toolIdRaw;
2700
+ const sourceKindRaw = sourceRef?.kind ? String(sourceRef.kind).trim().toLowerCase() : "";
2701
+ const sourceKind = sourceKindRaw === "" ? "unknown" : sourceKindRaw;
2702
+ const sourceHashRaw = sourceRef?.hash ? String(sourceRef.hash).trim().toLowerCase() : "";
2703
+ const sourceHash = sourceHashRaw === "" ? null : sourceHashRaw;
2704
+ const eventKindRaw = artifact?.eventKind ? String(artifact.eventKind).trim().toLowerCase() : "";
2705
+ const eventKind = eventKindRaw === "" ? "unknown" : eventKindRaw;
2706
+ const occurredAtParsed = Date.parse(String(artifact.occurredAt ?? ""));
2707
+ const occurredAt = Number.isFinite(occurredAtParsed) ? new Date(occurredAtParsed).toISOString() : new Date().toISOString();
2708
+
2709
+ await client.query(
2710
+ `
2711
+ INSERT INTO reputation_event_index (
2712
+ tenant_id, artifact_id, artifact_hash, subject_agent_id, subject_tool_id, occurred_at, event_kind, source_kind, source_hash
2713
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
2714
+ ON CONFLICT (tenant_id, artifact_id) DO UPDATE SET
2715
+ artifact_hash = EXCLUDED.artifact_hash,
2716
+ subject_agent_id = EXCLUDED.subject_agent_id,
2717
+ subject_tool_id = EXCLUDED.subject_tool_id,
2718
+ occurred_at = EXCLUDED.occurred_at,
2719
+ event_kind = EXCLUDED.event_kind,
2720
+ source_kind = EXCLUDED.source_kind,
2721
+ source_hash = EXCLUDED.source_hash
2722
+ `,
2723
+ [tenantId, String(artifactId), String(artifactHash), agentId, toolId, occurredAt, eventKind, sourceKind, sourceHash]
2724
+ );
2725
+ }
2726
+
2031
2727
  async function persistArtifactRow(client, { tenantId, artifact }) {
2032
2728
  tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
2033
2729
  if (!artifact || typeof artifact !== "object") throw new TypeError("artifact is required");
@@ -2040,9 +2736,12 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
2040
2736
  const jobId = String(artifact.jobId ?? "");
2041
2737
  const sourceEventId = typeof artifact.sourceEventId === "string" && artifact.sourceEventId.trim() !== "" ? String(artifact.sourceEventId) : "";
2042
2738
 
2043
- // Invariant: for artifacts tied to a specific source event, there must be exactly one artifact per
2739
+ // Invariant: for artifacts tied to a specific *job* source event, there must be exactly one artifact per
2044
2740
  // (jobId + artifactType + sourceEventId). This prevents duplicate settlement-backed certificates.
2045
- if (sourceEventId) {
2741
+ //
2742
+ // Important: many non-job artifacts (month close statements, party statements, payout instructions, etc.) set a
2743
+ // sourceEventId but intentionally do not have a jobId. Do not apply this invariant to those artifacts.
2744
+ if (sourceEventId && jobId) {
2046
2745
  const existingBySource = await client.query(
2047
2746
  "SELECT artifact_id, artifact_hash FROM artifacts WHERE tenant_id = $1 AND job_id = $2 AND artifact_type = $3 AND source_event_id = $4 LIMIT 1",
2048
2747
  [tenantId, jobId, artifactType, sourceEventId]
@@ -2058,80 +2757,89 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
2058
2757
  err.gotArtifactHash = String(artifactHash);
2059
2758
  throw err;
2060
2759
  }
2760
+ await persistReputationEventIndexRow(client, { tenantId, artifactId: currentId, artifactHash: currentHash, artifact });
2061
2761
  return;
2062
2762
  }
2063
2763
  }
2064
2764
 
2065
- const existing = await client.query("SELECT artifact_hash FROM artifacts WHERE tenant_id = $1 AND artifact_id = $2", [tenantId, artifactId]);
2066
- if (existing.rows.length) {
2067
- const current = String(existing.rows[0].artifact_hash);
2068
- if (current !== String(artifactHash)) {
2069
- const err = new Error("artifactId already exists with a different hash");
2070
- err.code = "ARTIFACT_HASH_MISMATCH";
2071
- err.expectedArtifactHash = current;
2072
- err.gotArtifactHash = String(artifactHash);
2073
- throw err;
2074
- }
2075
- return;
2076
- }
2765
+ const existing = await client.query("SELECT artifact_hash FROM artifacts WHERE tenant_id = $1 AND artifact_id = $2", [tenantId, artifactId]);
2766
+ if (existing.rows.length) {
2767
+ const current = String(existing.rows[0].artifact_hash);
2768
+ if (current !== String(artifactHash)) {
2769
+ const err = new Error("artifactId already exists with a different hash");
2770
+ err.code = "ARTIFACT_HASH_MISMATCH";
2771
+ err.expectedArtifactHash = current;
2772
+ err.gotArtifactHash = String(artifactHash);
2773
+ throw err;
2774
+ }
2775
+ await persistReputationEventIndexRow(client, { tenantId, artifactId, artifactHash: current, artifact });
2776
+ return;
2777
+ }
2778
+
2779
+ // Avoid transaction-aborting unique-constraint errors (we run inside explicit transactions).
2780
+ // Handle all unique conflicts by doing nothing and then checking what already exists.
2781
+ const insertRes = await client.query(
2782
+ `
2783
+ INSERT INTO artifacts (tenant_id, artifact_id, artifact_type, job_id, at_chain_hash, source_event_id, artifact_hash, artifact_json)
2784
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
2785
+ ON CONFLICT DO NOTHING
2786
+ `,
2787
+ [
2788
+ tenantId,
2789
+ String(artifactId),
2790
+ artifactType,
2791
+ jobId,
2792
+ String(artifact.atChainHash ?? artifact.eventProof?.lastChainHash ?? ""),
2793
+ sourceEventId,
2794
+ String(artifactHash),
2795
+ JSON.stringify(artifact)
2796
+ ]
2797
+ );
2798
+ if (Number(insertRes?.rowCount ?? 0) > 0) {
2799
+ await persistReputationEventIndexRow(client, { tenantId, artifactId, artifactHash, artifact });
2800
+ return;
2801
+ }
2077
2802
 
2078
- try {
2079
- await client.query(
2080
- `
2081
- INSERT INTO artifacts (tenant_id, artifact_id, artifact_type, job_id, at_chain_hash, source_event_id, artifact_hash, artifact_json)
2082
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
2083
- `,
2084
- [
2085
- tenantId,
2086
- String(artifactId),
2087
- artifactType,
2088
- jobId,
2089
- String(artifact.atChainHash ?? artifact.eventProof?.lastChainHash ?? ""),
2090
- sourceEventId,
2091
- String(artifactHash),
2092
- JSON.stringify(artifact)
2093
- ]
2094
- );
2095
- } catch (err) {
2096
- // Under concurrency, two workers may attempt to persist the same artifact at the same time. Treat this as
2097
- // idempotent when the existing row's hash matches, and retryable otherwise.
2098
- if (err?.code === "23505") {
2099
- if (sourceEventId) {
2100
- const bySource = await client.query(
2101
- "SELECT artifact_id, artifact_hash FROM artifacts WHERE tenant_id = $1 AND job_id = $2 AND artifact_type = $3 AND source_event_id = $4 LIMIT 1",
2102
- [tenantId, jobId, artifactType, sourceEventId]
2103
- );
2104
- if (bySource.rows.length) {
2105
- const currentId = String(bySource.rows[0].artifact_id);
2106
- const currentHash = String(bySource.rows[0].artifact_hash);
2107
- if (currentHash === String(artifactHash)) return;
2108
- const conflict = new Error("artifact already exists for this job/type/sourceEventId with a different hash");
2109
- conflict.code = "ARTIFACT_SOURCE_EVENT_CONFLICT";
2110
- conflict.existingArtifactId = currentId;
2111
- conflict.existingArtifactHash = currentHash;
2112
- conflict.gotArtifactHash = String(artifactHash);
2113
- throw conflict;
2114
- }
2115
- }
2803
+ // Someone else inserted a conflicting row under a unique constraint. Determine if it's idempotent.
2804
+ const byId = await client.query("SELECT artifact_hash FROM artifacts WHERE tenant_id = $1 AND artifact_id = $2", [tenantId, artifactId]);
2805
+ if (byId.rows.length) {
2806
+ const current = String(byId.rows[0].artifact_hash);
2807
+ if (current === String(artifactHash)) {
2808
+ await persistReputationEventIndexRow(client, { tenantId, artifactId, artifactHash: current, artifact });
2809
+ return;
2810
+ }
2811
+ const mismatch = new Error("artifactId already exists with a different hash");
2812
+ mismatch.code = "ARTIFACT_HASH_MISMATCH";
2813
+ mismatch.expectedArtifactHash = current;
2814
+ mismatch.gotArtifactHash = String(artifactHash);
2815
+ throw mismatch;
2816
+ }
2116
2817
 
2117
- const byId = await client.query("SELECT artifact_hash FROM artifacts WHERE tenant_id = $1 AND artifact_id = $2", [tenantId, artifactId]);
2118
- if (byId.rows.length) {
2119
- const current = String(byId.rows[0].artifact_hash);
2120
- if (current === String(artifactHash)) return;
2121
- const mismatch = new Error("artifactId already exists with a different hash");
2122
- mismatch.code = "ARTIFACT_HASH_MISMATCH";
2123
- mismatch.expectedArtifactHash = current;
2124
- mismatch.gotArtifactHash = String(artifactHash);
2125
- throw mismatch;
2126
- }
2818
+ if (sourceEventId && jobId) {
2819
+ const bySource = await client.query(
2820
+ "SELECT artifact_id, artifact_hash FROM artifacts WHERE tenant_id = $1 AND job_id = $2 AND artifact_type = $3 AND source_event_id = $4 LIMIT 1",
2821
+ [tenantId, jobId, artifactType, sourceEventId]
2822
+ );
2823
+ if (bySource.rows.length) {
2824
+ const currentId = String(bySource.rows[0].artifact_id);
2825
+ const currentHash = String(bySource.rows[0].artifact_hash);
2826
+ if (currentHash === String(artifactHash)) {
2827
+ await persistReputationEventIndexRow(client, { tenantId, artifactId: currentId, artifactHash: currentHash, artifact });
2828
+ return;
2829
+ }
2830
+ const conflict = new Error("artifact already exists for this job/type/sourceEventId with a different hash");
2831
+ conflict.code = "ARTIFACT_SOURCE_EVENT_CONFLICT";
2832
+ conflict.existingArtifactId = currentId;
2833
+ conflict.existingArtifactHash = currentHash;
2834
+ conflict.gotArtifactHash = String(artifactHash);
2835
+ throw conflict;
2836
+ }
2837
+ }
2127
2838
 
2128
- const raced = new Error("artifact insert raced with another transaction");
2129
- raced.code = "ARTIFACT_INSERT_RACE";
2130
- throw raced;
2131
- }
2132
- throw err;
2133
- }
2134
- }
2839
+ const raced = new Error("artifact insert raced with another transaction");
2840
+ raced.code = "ARTIFACT_INSERT_RACE";
2841
+ throw raced;
2842
+ }
2135
2843
 
2136
2844
  async function insertDeliveryRow(client, { tenantId, delivery }) {
2137
2845
  tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
@@ -2398,6 +3106,10 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
2398
3106
  const tenantId = normalizeTenantId(op.tenantId ?? op.agentIdentity?.tenantId ?? DEFAULT_TENANT_ID);
2399
3107
  await persistAgentIdentity(client, { tenantId, agentIdentity: op.agentIdentity });
2400
3108
  }
3109
+ if (op.kind === "AGENT_PASSPORT_UPSERT") {
3110
+ const tenantId = normalizeTenantId(op.tenantId ?? op.agentPassport?.tenantId ?? DEFAULT_TENANT_ID);
3111
+ await persistAgentPassport(client, { tenantId, agentId: op.agentId, agentPassport: op.agentPassport });
3112
+ }
2401
3113
  if (op.kind === "AGENT_WALLET_UPSERT") {
2402
3114
  const tenantId = normalizeTenantId(op.tenantId ?? op.wallet?.tenantId ?? DEFAULT_TENANT_ID);
2403
3115
  await persistAgentWallet(client, { tenantId, wallet: op.wallet });
@@ -2415,6 +3127,85 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
2415
3127
  const tenantId = normalizeTenantId(op.tenantId ?? op.arbitrationCase?.tenantId ?? DEFAULT_TENANT_ID);
2416
3128
  await persistArbitrationCase(client, { tenantId, caseId: op.caseId, arbitrationCase: op.arbitrationCase });
2417
3129
  }
3130
+ if (op.kind === "AGREEMENT_DELEGATION_UPSERT") {
3131
+ const tenantId = normalizeTenantId(op.tenantId ?? op.delegation?.tenantId ?? DEFAULT_TENANT_ID);
3132
+ await persistAgreementDelegation(client, { tenantId, delegationId: op.delegationId, delegation: op.delegation });
3133
+ }
3134
+ if (op.kind === "X402_GATE_UPSERT") {
3135
+ const tenantId = normalizeTenantId(op.tenantId ?? op.gate?.tenantId ?? DEFAULT_TENANT_ID);
3136
+ await persistX402Gate(client, { tenantId, gateId: op.gateId, gate: op.gate });
3137
+ }
3138
+ if (op.kind === "X402_AGENT_LIFECYCLE_UPSERT") {
3139
+ const tenantId = normalizeTenantId(op.tenantId ?? op.agentLifecycle?.tenantId ?? DEFAULT_TENANT_ID);
3140
+ await persistX402AgentLifecycle(client, { tenantId, agentId: op.agentId, agentLifecycle: op.agentLifecycle });
3141
+ }
3142
+ if (op.kind === "X402_RECEIPT_PUT") {
3143
+ const tenantId = normalizeTenantId(op.tenantId ?? op.receipt?.tenantId ?? DEFAULT_TENANT_ID);
3144
+ await persistX402Receipt(client, { tenantId, receiptId: op.receiptId, receipt: op.receipt });
3145
+ }
3146
+ if (op.kind === "X402_WALLET_POLICY_UPSERT") {
3147
+ const tenantId = normalizeTenantId(op.tenantId ?? op.policy?.tenantId ?? DEFAULT_TENANT_ID);
3148
+ await persistX402WalletPolicy(client, { tenantId, policy: op.policy });
3149
+ }
3150
+ if (op.kind === "X402_ESCALATION_UPSERT") {
3151
+ const tenantId = normalizeTenantId(op.tenantId ?? op.escalation?.tenantId ?? DEFAULT_TENANT_ID);
3152
+ await persistX402Escalation(client, {
3153
+ tenantId,
3154
+ escalationId: op.escalationId,
3155
+ escalation: op.escalation
3156
+ });
3157
+ }
3158
+ if (op.kind === "X402_ESCALATION_EVENT_APPEND") {
3159
+ const tenantId = normalizeTenantId(op.tenantId ?? op.event?.tenantId ?? DEFAULT_TENANT_ID);
3160
+ await persistX402EscalationEvent(client, {
3161
+ tenantId,
3162
+ eventId: op.eventId,
3163
+ escalationId: op.escalationId,
3164
+ event: op.event
3165
+ });
3166
+ }
3167
+ if (op.kind === "X402_ESCALATION_OVERRIDE_USAGE_PUT") {
3168
+ const tenantId = normalizeTenantId(op.tenantId ?? op.usage?.tenantId ?? DEFAULT_TENANT_ID);
3169
+ await persistX402EscalationOverrideUsage(client, {
3170
+ tenantId,
3171
+ overrideId: op.overrideId,
3172
+ usage: op.usage
3173
+ });
3174
+ }
3175
+ if (op.kind === "X402_ZK_VERIFICATION_KEY_PUT") {
3176
+ const tenantId = normalizeTenantId(op.tenantId ?? op.verificationKey?.tenantId ?? DEFAULT_TENANT_ID);
3177
+ await persistX402ZkVerificationKey(client, {
3178
+ tenantId,
3179
+ verificationKeyId: op.verificationKeyId,
3180
+ verificationKey: op.verificationKey
3181
+ });
3182
+ }
3183
+ if (op.kind === "X402_REVERSAL_EVENT_APPEND") {
3184
+ const tenantId = normalizeTenantId(op.tenantId ?? op.event?.tenantId ?? DEFAULT_TENANT_ID);
3185
+ await persistX402ReversalEvent(client, {
3186
+ tenantId,
3187
+ gateId: op.gateId,
3188
+ eventId: op.eventId,
3189
+ event: op.event
3190
+ });
3191
+ }
3192
+ if (op.kind === "X402_REVERSAL_NONCE_PUT") {
3193
+ const tenantId = normalizeTenantId(op.tenantId ?? op.usage?.tenantId ?? DEFAULT_TENANT_ID);
3194
+ await persistX402ReversalNonceUsage(client, {
3195
+ tenantId,
3196
+ sponsorRef: op.sponsorRef,
3197
+ nonce: op.nonce,
3198
+ usage: op.usage
3199
+ });
3200
+ }
3201
+ if (op.kind === "X402_REVERSAL_COMMAND_PUT") {
3202
+ const tenantId = normalizeTenantId(op.tenantId ?? op.usage?.tenantId ?? DEFAULT_TENANT_ID);
3203
+ await persistX402ReversalCommandUsage(client, {
3204
+ tenantId,
3205
+ commandId: op.commandId,
3206
+ usage: op.usage
3207
+ });
3208
+ }
2418
3209
  if (op.kind === "TOOL_CALL_HOLD_UPSERT") {
2419
3210
  const tenantId = normalizeTenantId(op.tenantId ?? op.hold?.tenantId ?? DEFAULT_TENANT_ID);
2420
3211
  await persistToolCallHold(client, { tenantId, holdHash: op.holdHash, hold: op.hold });
@@ -2472,54 +3263,404 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
2472
3263
  }
2473
3264
  }
2474
3265
 
2475
- if (audit) {
2476
- const tenantId = normalizeTenantId(audit?.tenantId ?? DEFAULT_TENANT_ID);
2477
- await insertOpsAuditRow(client, { tenantId, audit });
3266
+ if (audit) {
3267
+ const tenantId = normalizeTenantId(audit?.tenantId ?? DEFAULT_TENANT_ID);
3268
+ await insertOpsAuditRow(client, { tenantId, audit });
3269
+ }
3270
+ });
3271
+
3272
+ if (hasEventsAppended) failpoint("pg.append.after_commit");
3273
+
3274
+ // Update in-memory projections for local process behavior (tests + single-process dev).
3275
+ try {
3276
+ applyTxRecord(store, { v: TX_LOG_VERSION, at: at ?? new Date().toISOString(), txId: "tx_pg", ops });
3277
+ } catch {
3278
+ // Ignore local projection update failures; DB is canonical.
3279
+ }
3280
+
3281
+ // Apply any pending outbox-driven side-effects.
3282
+ await store.processOutbox({ maxMessages: 1000 });
3283
+ };
3284
+
3285
+ store.refreshFromDb = async function refreshFromDb() {
3286
+ await refreshSnapshots();
3287
+ await refreshEvents();
3288
+ await refreshIdempotency();
3289
+ await refreshLedgerBalances();
3290
+ await refreshContracts();
3291
+ await refreshTenantBillingConfigs();
3292
+ await refreshMarketplaceRfqs();
3293
+ await refreshMarketplaceRfqBids();
3294
+ await refreshTenantSettlementPolicies();
3295
+ await refreshAgentIdentities();
3296
+ await refreshAgentWallets();
3297
+ await refreshAgentRunSettlements();
3298
+ await refreshAuthKeys();
3299
+ await refreshSignerKeys();
3300
+ };
3301
+
3302
+ function agentRunSnapshotRowToRecord(row) {
3303
+ const run = row?.snapshot_json ?? null;
3304
+ if (!run || typeof run !== "object" || Array.isArray(run)) return null;
3305
+ const tenantId = normalizeTenantId(row?.tenant_id ?? run?.tenantId ?? DEFAULT_TENANT_ID);
3306
+ const runId = row?.aggregate_id ? String(row.aggregate_id) : run?.runId ? String(run.runId) : null;
3307
+ if (!runId) return null;
3308
+ return {
3309
+ ...run,
3310
+ tenantId,
3311
+ runId
3312
+ };
3313
+ }
3314
+
3315
+ function x402WalletPolicySnapshotRowToRecord(row) {
3316
+ const policy = row?.snapshot_json ?? null;
3317
+ if (!policy || typeof policy !== "object" || Array.isArray(policy)) return null;
3318
+ const tenantId = normalizeTenantId(row?.tenant_id ?? policy?.tenantId ?? DEFAULT_TENANT_ID);
3319
+ const sponsorWalletRef =
3320
+ typeof policy?.sponsorWalletRef === "string" && policy.sponsorWalletRef.trim() !== "" ? policy.sponsorWalletRef.trim() : null;
3321
+ const policyRef = typeof policy?.policyRef === "string" && policy.policyRef.trim() !== "" ? policy.policyRef.trim() : null;
3322
+ const policyVersion = parseSafeIntegerOrNull(policy?.policyVersion);
3323
+ if (!sponsorWalletRef || !policyRef || policyVersion === null || policyVersion <= 0) return null;
3324
+ return {
3325
+ ...policy,
3326
+ tenantId,
3327
+ sponsorWalletRef,
3328
+ policyRef,
3329
+ policyVersion
3330
+ };
3331
+ }
3332
+
3333
+ function x402EscalationSnapshotRowToRecord(row) {
3334
+ const escalation = row?.snapshot_json ?? null;
3335
+ if (!escalation || typeof escalation !== "object" || Array.isArray(escalation)) return null;
3336
+ const tenantId = normalizeTenantId(row?.tenant_id ?? escalation?.tenantId ?? DEFAULT_TENANT_ID);
3337
+ const escalationId =
3338
+ row?.aggregate_id && String(row.aggregate_id).trim() !== ""
3339
+ ? String(row.aggregate_id).trim()
3340
+ : typeof escalation?.escalationId === "string" && escalation.escalationId.trim() !== ""
3341
+ ? escalation.escalationId.trim()
3342
+ : null;
3343
+ if (!escalationId) return null;
3344
+ return {
3345
+ ...escalation,
3346
+ tenantId,
3347
+ escalationId
3348
+ };
3349
+ }
3350
+
3351
+ function x402EscalationEventSnapshotRowToRecord(row) {
3352
+ const event = row?.snapshot_json ?? null;
3353
+ if (!event || typeof event !== "object" || Array.isArray(event)) return null;
3354
+ const tenantId = normalizeTenantId(row?.tenant_id ?? event?.tenantId ?? DEFAULT_TENANT_ID);
3355
+ const eventId =
3356
+ row?.aggregate_id && String(row.aggregate_id).trim() !== ""
3357
+ ? String(row.aggregate_id).trim()
3358
+ : typeof event?.eventId === "string" && event.eventId.trim() !== ""
3359
+ ? event.eventId.trim()
3360
+ : null;
3361
+ const escalationId =
3362
+ typeof event?.escalationId === "string" && event.escalationId.trim() !== "" ? event.escalationId.trim() : null;
3363
+ if (!eventId || !escalationId) return null;
3364
+ return {
3365
+ ...event,
3366
+ tenantId,
3367
+ eventId,
3368
+ escalationId
3369
+ };
3370
+ }
3371
+
3372
+ function x402EscalationOverrideUsageSnapshotRowToRecord(row) {
3373
+ const usage = row?.snapshot_json ?? null;
3374
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) return null;
3375
+ const tenantId = normalizeTenantId(row?.tenant_id ?? usage?.tenantId ?? DEFAULT_TENANT_ID);
3376
+ const overrideId =
3377
+ row?.aggregate_id && String(row.aggregate_id).trim() !== ""
3378
+ ? String(row.aggregate_id).trim()
3379
+ : typeof usage?.overrideId === "string" && usage.overrideId.trim() !== ""
3380
+ ? usage.overrideId.trim()
3381
+ : null;
3382
+ if (!overrideId) return null;
3383
+ return {
3384
+ ...usage,
3385
+ tenantId,
3386
+ overrideId
3387
+ };
3388
+ }
3389
+
3390
+ store.getX402WalletPolicy = async function getX402WalletPolicy({
3391
+ tenantId = DEFAULT_TENANT_ID,
3392
+ sponsorWalletRef,
3393
+ policyRef,
3394
+ policyVersion
3395
+ } = {}) {
3396
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3397
+ assertNonEmptyString(sponsorWalletRef, "sponsorWalletRef");
3398
+ assertNonEmptyString(policyRef, "policyRef");
3399
+ const safePolicyVersion = parseSafeIntegerOrNull(policyVersion);
3400
+ if (safePolicyVersion === null || safePolicyVersion <= 0) {
3401
+ throw new TypeError("policyVersion must be a positive safe integer");
3402
+ }
3403
+ const aggregateId = `${String(sponsorWalletRef).trim()}::${String(policyRef).trim()}::${safePolicyVersion}`;
3404
+ try {
3405
+ const res = await pool.query(
3406
+ `
3407
+ SELECT tenant_id, aggregate_id, snapshot_json
3408
+ FROM snapshots
3409
+ WHERE tenant_id = $1 AND aggregate_type = 'x402_wallet_policy' AND aggregate_id = $2
3410
+ LIMIT 1
3411
+ `,
3412
+ [tenantId, aggregateId]
3413
+ );
3414
+ return res.rows.length ? x402WalletPolicySnapshotRowToRecord(res.rows[0]) : null;
3415
+ } catch (err) {
3416
+ if (err?.code !== "42P01") throw err;
3417
+ return (
3418
+ store.x402WalletPolicies.get(
3419
+ makeScopedKey({
3420
+ tenantId,
3421
+ id: aggregateId
3422
+ })
3423
+ ) ?? null
3424
+ );
3425
+ }
3426
+ };
3427
+
3428
+ store.listX402WalletPolicies = async function listX402WalletPolicies({
3429
+ tenantId = DEFAULT_TENANT_ID,
3430
+ sponsorWalletRef = null,
3431
+ sponsorRef = null,
3432
+ policyRef = null,
3433
+ status = null,
3434
+ limit = 200,
3435
+ offset = 0
3436
+ } = {}) {
3437
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3438
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new TypeError("limit must be a positive safe integer");
3439
+ if (!Number.isSafeInteger(offset) || offset < 0) throw new TypeError("offset must be a non-negative safe integer");
3440
+ const safeLimit = Math.min(1000, limit);
3441
+ const safeOffset = offset;
3442
+ const sponsorWalletFilter =
3443
+ sponsorWalletRef === null || sponsorWalletRef === undefined || String(sponsorWalletRef).trim() === ""
3444
+ ? null
3445
+ : String(sponsorWalletRef).trim();
3446
+ const sponsorFilter = sponsorRef === null || sponsorRef === undefined || String(sponsorRef).trim() === "" ? null : String(sponsorRef).trim();
3447
+ const policyFilter = policyRef === null || policyRef === undefined || String(policyRef).trim() === "" ? null : String(policyRef).trim();
3448
+ const statusFilter = status === null || status === undefined || String(status).trim() === "" ? null : String(status).trim().toLowerCase();
3449
+ try {
3450
+ const res = await pool.query(
3451
+ `
3452
+ SELECT tenant_id, aggregate_id, snapshot_json
3453
+ FROM snapshots
3454
+ WHERE tenant_id = $1 AND aggregate_type = 'x402_wallet_policy'
3455
+ ORDER BY updated_at DESC, aggregate_id ASC
3456
+ `,
3457
+ [tenantId]
3458
+ );
3459
+ const rows = res.rows.map(x402WalletPolicySnapshotRowToRecord).filter(Boolean);
3460
+ const filtered = [];
3461
+ for (const row of rows) {
3462
+ if (sponsorWalletFilter && String(row.sponsorWalletRef ?? "") !== sponsorWalletFilter) continue;
3463
+ if (sponsorFilter && String(row.sponsorRef ?? "") !== sponsorFilter) continue;
3464
+ if (policyFilter && String(row.policyRef ?? "") !== policyFilter) continue;
3465
+ if (statusFilter && String(row.status ?? "").toLowerCase() !== statusFilter) continue;
3466
+ filtered.push(row);
2478
3467
  }
2479
- });
2480
-
2481
- if (hasEventsAppended) failpoint("pg.append.after_commit");
3468
+ filtered.sort((left, right) => {
3469
+ const leftAt = Number.isFinite(Date.parse(String(left?.updatedAt ?? ""))) ? Date.parse(String(left.updatedAt)) : Number.NaN;
3470
+ const rightAt = Number.isFinite(Date.parse(String(right?.updatedAt ?? ""))) ? Date.parse(String(right.updatedAt)) : Number.NaN;
3471
+ if (Number.isFinite(leftAt) && Number.isFinite(rightAt) && rightAt !== leftAt) return rightAt - leftAt;
3472
+ const sponsorOrder = String(left?.sponsorWalletRef ?? "").localeCompare(String(right?.sponsorWalletRef ?? ""));
3473
+ if (sponsorOrder !== 0) return sponsorOrder;
3474
+ const policyOrder = String(left?.policyRef ?? "").localeCompare(String(right?.policyRef ?? ""));
3475
+ if (policyOrder !== 0) return policyOrder;
3476
+ return Number(right?.policyVersion ?? 0) - Number(left?.policyVersion ?? 0);
3477
+ });
3478
+ return filtered.slice(safeOffset, safeOffset + safeLimit);
3479
+ } catch (err) {
3480
+ if (err?.code !== "42P01") throw err;
3481
+ const out = [];
3482
+ for (const row of store.x402WalletPolicies.values()) {
3483
+ if (!row || typeof row !== "object" || Array.isArray(row)) continue;
3484
+ if (normalizeTenantId(row.tenantId ?? DEFAULT_TENANT_ID) !== tenantId) continue;
3485
+ if (sponsorWalletFilter && String(row.sponsorWalletRef ?? "") !== sponsorWalletFilter) continue;
3486
+ if (sponsorFilter && String(row.sponsorRef ?? "") !== sponsorFilter) continue;
3487
+ if (policyFilter && String(row.policyRef ?? "") !== policyFilter) continue;
3488
+ if (statusFilter && String(row.status ?? "").toLowerCase() !== statusFilter) continue;
3489
+ out.push(row);
3490
+ }
3491
+ out.sort((left, right) => {
3492
+ const leftAt = Number.isFinite(Date.parse(String(left?.updatedAt ?? ""))) ? Date.parse(String(left.updatedAt)) : Number.NaN;
3493
+ const rightAt = Number.isFinite(Date.parse(String(right?.updatedAt ?? ""))) ? Date.parse(String(right.updatedAt)) : Number.NaN;
3494
+ if (Number.isFinite(leftAt) && Number.isFinite(rightAt) && rightAt !== leftAt) return rightAt - leftAt;
3495
+ const sponsorOrder = String(left?.sponsorWalletRef ?? "").localeCompare(String(right?.sponsorWalletRef ?? ""));
3496
+ if (sponsorOrder !== 0) return sponsorOrder;
3497
+ const policyOrder = String(left?.policyRef ?? "").localeCompare(String(right?.policyRef ?? ""));
3498
+ if (policyOrder !== 0) return policyOrder;
3499
+ return Number(right?.policyVersion ?? 0) - Number(left?.policyVersion ?? 0);
3500
+ });
3501
+ return out.slice(safeOffset, safeOffset + safeLimit);
3502
+ }
3503
+ };
2482
3504
 
2483
- // Update in-memory projections for local process behavior (tests + single-process dev).
3505
+ store.getX402Escalation = async function getX402Escalation({ tenantId = DEFAULT_TENANT_ID, escalationId } = {}) {
3506
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3507
+ assertNonEmptyString(escalationId, "escalationId");
3508
+ const normalizedEscalationId = String(escalationId).trim();
2484
3509
  try {
2485
- applyTxRecord(store, { v: TX_LOG_VERSION, at: at ?? new Date().toISOString(), txId: "tx_pg", ops });
2486
- } catch {
2487
- // Ignore local projection update failures; DB is canonical.
3510
+ const res = await pool.query(
3511
+ `
3512
+ SELECT tenant_id, aggregate_id, snapshot_json
3513
+ FROM snapshots
3514
+ WHERE tenant_id = $1 AND aggregate_type = 'x402_escalation' AND aggregate_id = $2
3515
+ LIMIT 1
3516
+ `,
3517
+ [tenantId, normalizedEscalationId]
3518
+ );
3519
+ return res.rows.length ? x402EscalationSnapshotRowToRecord(res.rows[0]) : null;
3520
+ } catch (err) {
3521
+ if (err?.code !== "42P01") throw err;
3522
+ return store.x402Escalations.get(makeScopedKey({ tenantId, id: normalizedEscalationId })) ?? null;
2488
3523
  }
3524
+ };
2489
3525
 
2490
- // Apply any pending outbox-driven side-effects.
2491
- await store.processOutbox({ maxMessages: 1000 });
3526
+ store.listX402Escalations = async function listX402Escalations({
3527
+ tenantId = DEFAULT_TENANT_ID,
3528
+ gateId = null,
3529
+ agentId = null,
3530
+ status = null,
3531
+ limit = 200,
3532
+ offset = 0
3533
+ } = {}) {
3534
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3535
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new TypeError("limit must be a positive safe integer");
3536
+ if (!Number.isSafeInteger(offset) || offset < 0) throw new TypeError("offset must be a non-negative safe integer");
3537
+ const safeLimit = Math.min(1000, limit);
3538
+ const safeOffset = offset;
3539
+ const gateFilter = gateId === null || gateId === undefined || String(gateId).trim() === "" ? null : String(gateId).trim();
3540
+ const agentFilter = agentId === null || agentId === undefined || String(agentId).trim() === "" ? null : String(agentId).trim();
3541
+ const statusFilter = status === null || status === undefined || String(status).trim() === "" ? null : String(status).trim().toLowerCase();
3542
+ try {
3543
+ const res = await pool.query(
3544
+ `
3545
+ SELECT tenant_id, aggregate_id, snapshot_json
3546
+ FROM snapshots
3547
+ WHERE tenant_id = $1 AND aggregate_type = 'x402_escalation'
3548
+ ORDER BY updated_at DESC, aggregate_id ASC
3549
+ `,
3550
+ [tenantId]
3551
+ );
3552
+ const rows = res.rows.map(x402EscalationSnapshotRowToRecord).filter(Boolean);
3553
+ const filtered = [];
3554
+ for (const row of rows) {
3555
+ if (gateFilter && String(row.gateId ?? "") !== gateFilter) continue;
3556
+ if (agentFilter && String(row.requesterAgentId ?? "") !== agentFilter) continue;
3557
+ if (statusFilter && String(row.status ?? "").toLowerCase() !== statusFilter) continue;
3558
+ filtered.push(row);
3559
+ }
3560
+ filtered.sort((left, right) => {
3561
+ const leftAt = Number.isFinite(Date.parse(String(left?.updatedAt ?? left?.createdAt ?? "")))
3562
+ ? Date.parse(String(left.updatedAt ?? left.createdAt))
3563
+ : Number.NaN;
3564
+ const rightAt = Number.isFinite(Date.parse(String(right?.updatedAt ?? right?.createdAt ?? "")))
3565
+ ? Date.parse(String(right.updatedAt ?? right.createdAt))
3566
+ : Number.NaN;
3567
+ if (Number.isFinite(leftAt) && Number.isFinite(rightAt) && rightAt !== leftAt) return rightAt - leftAt;
3568
+ return String(left?.escalationId ?? "").localeCompare(String(right?.escalationId ?? ""));
3569
+ });
3570
+ return filtered.slice(safeOffset, safeOffset + safeLimit);
3571
+ } catch (err) {
3572
+ if (err?.code !== "42P01") throw err;
3573
+ const out = [];
3574
+ for (const row of store.x402Escalations.values()) {
3575
+ if (!row || typeof row !== "object" || Array.isArray(row)) continue;
3576
+ if (normalizeTenantId(row.tenantId ?? DEFAULT_TENANT_ID) !== tenantId) continue;
3577
+ if (gateFilter && String(row.gateId ?? "") !== gateFilter) continue;
3578
+ if (agentFilter && String(row.requesterAgentId ?? "") !== agentFilter) continue;
3579
+ if (statusFilter && String(row.status ?? "").toLowerCase() !== statusFilter) continue;
3580
+ out.push(row);
3581
+ }
3582
+ out.sort((left, right) => {
3583
+ const leftAt = Number.isFinite(Date.parse(String(left?.updatedAt ?? left?.createdAt ?? "")))
3584
+ ? Date.parse(String(left.updatedAt ?? left.createdAt))
3585
+ : Number.NaN;
3586
+ const rightAt = Number.isFinite(Date.parse(String(right?.updatedAt ?? right?.createdAt ?? "")))
3587
+ ? Date.parse(String(right.updatedAt ?? right.createdAt))
3588
+ : Number.NaN;
3589
+ if (Number.isFinite(leftAt) && Number.isFinite(rightAt) && rightAt !== leftAt) return rightAt - leftAt;
3590
+ return String(left?.escalationId ?? "").localeCompare(String(right?.escalationId ?? ""));
3591
+ });
3592
+ return out.slice(safeOffset, safeOffset + safeLimit);
3593
+ }
2492
3594
  };
2493
3595
 
2494
- store.refreshFromDb = async function refreshFromDb() {
2495
- await refreshSnapshots();
2496
- await refreshEvents();
2497
- await refreshIdempotency();
2498
- await refreshLedgerBalances();
2499
- await refreshContracts();
2500
- await refreshTenantBillingConfigs();
2501
- await refreshMarketplaceRfqs();
2502
- await refreshMarketplaceRfqBids();
2503
- await refreshTenantSettlementPolicies();
2504
- await refreshAgentIdentities();
2505
- await refreshAgentWallets();
2506
- await refreshAgentRunSettlements();
2507
- await refreshAuthKeys();
2508
- await refreshSignerKeys();
3596
+ store.listX402EscalationEvents = async function listX402EscalationEvents({
3597
+ tenantId = DEFAULT_TENANT_ID,
3598
+ escalationId = null,
3599
+ limit = 200,
3600
+ offset = 0
3601
+ } = {}) {
3602
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3603
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new TypeError("limit must be a positive safe integer");
3604
+ if (!Number.isSafeInteger(offset) || offset < 0) throw new TypeError("offset must be a non-negative safe integer");
3605
+ const safeLimit = Math.min(1000, limit);
3606
+ const safeOffset = offset;
3607
+ const escalationFilter =
3608
+ escalationId === null || escalationId === undefined || String(escalationId).trim() === "" ? null : String(escalationId).trim();
3609
+ try {
3610
+ const res = await pool.query(
3611
+ `
3612
+ SELECT tenant_id, aggregate_id, snapshot_json
3613
+ FROM snapshots
3614
+ WHERE tenant_id = $1 AND aggregate_type = 'x402_escalation_event'
3615
+ ORDER BY updated_at ASC, aggregate_id ASC
3616
+ `,
3617
+ [tenantId]
3618
+ );
3619
+ const rows = res.rows.map(x402EscalationEventSnapshotRowToRecord).filter(Boolean);
3620
+ const filtered = escalationFilter ? rows.filter((row) => String(row.escalationId ?? "") === escalationFilter) : rows;
3621
+ return filtered.slice(safeOffset, safeOffset + safeLimit);
3622
+ } catch (err) {
3623
+ if (err?.code !== "42P01") throw err;
3624
+ const out = [];
3625
+ for (const row of store.x402EscalationEvents.values()) {
3626
+ if (!row || typeof row !== "object" || Array.isArray(row)) continue;
3627
+ if (normalizeTenantId(row.tenantId ?? DEFAULT_TENANT_ID) !== tenantId) continue;
3628
+ if (escalationFilter && String(row.escalationId ?? "") !== escalationFilter) continue;
3629
+ out.push(row);
3630
+ }
3631
+ out.sort((left, right) => {
3632
+ const leftAt = Number.isFinite(Date.parse(String(left?.occurredAt ?? ""))) ? Date.parse(String(left.occurredAt)) : Number.NaN;
3633
+ const rightAt = Number.isFinite(Date.parse(String(right?.occurredAt ?? ""))) ? Date.parse(String(right.occurredAt)) : Number.NaN;
3634
+ if (Number.isFinite(leftAt) && Number.isFinite(rightAt) && leftAt !== rightAt) return leftAt - rightAt;
3635
+ return String(left?.eventId ?? "").localeCompare(String(right?.eventId ?? ""));
3636
+ });
3637
+ return out.slice(safeOffset, safeOffset + safeLimit);
3638
+ }
2509
3639
  };
2510
3640
 
2511
- function agentRunSnapshotRowToRecord(row) {
2512
- const run = row?.snapshot_json ?? null;
2513
- if (!run || typeof run !== "object" || Array.isArray(run)) return null;
2514
- const tenantId = normalizeTenantId(row?.tenant_id ?? run?.tenantId ?? DEFAULT_TENANT_ID);
2515
- const runId = row?.aggregate_id ? String(row.aggregate_id) : run?.runId ? String(run.runId) : null;
2516
- if (!runId) return null;
2517
- return {
2518
- ...run,
2519
- tenantId,
2520
- runId
2521
- };
2522
- }
3641
+ store.getX402EscalationOverrideUsage = async function getX402EscalationOverrideUsage({
3642
+ tenantId = DEFAULT_TENANT_ID,
3643
+ overrideId
3644
+ } = {}) {
3645
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3646
+ assertNonEmptyString(overrideId, "overrideId");
3647
+ const normalizedOverrideId = String(overrideId).trim();
3648
+ try {
3649
+ const res = await pool.query(
3650
+ `
3651
+ SELECT tenant_id, aggregate_id, snapshot_json
3652
+ FROM snapshots
3653
+ WHERE tenant_id = $1 AND aggregate_type = 'x402_escalation_override_usage' AND aggregate_id = $2
3654
+ LIMIT 1
3655
+ `,
3656
+ [tenantId, normalizedOverrideId]
3657
+ );
3658
+ return res.rows.length ? x402EscalationOverrideUsageSnapshotRowToRecord(res.rows[0]) : null;
3659
+ } catch (err) {
3660
+ if (err?.code !== "42P01") throw err;
3661
+ return store.x402EscalationOverrideUsage.get(makeScopedKey({ tenantId, id: normalizedOverrideId })) ?? null;
3662
+ }
3663
+ };
2523
3664
 
2524
3665
  store.getAgentIdentity = async function getAgentIdentity({ tenantId = DEFAULT_TENANT_ID, agentId } = {}) {
2525
3666
  tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
@@ -2585,6 +3726,41 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
2585
3726
  }
2586
3727
  };
2587
3728
 
3729
+ store.getAgentPassport = async function getAgentPassport({ tenantId = DEFAULT_TENANT_ID, agentId } = {}) {
3730
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3731
+ assertNonEmptyString(agentId, "agentId");
3732
+ const normalizedAgentId = String(agentId).trim();
3733
+ try {
3734
+ const res = await pool.query(
3735
+ `
3736
+ SELECT tenant_id, aggregate_id, snapshot_json, updated_at
3737
+ FROM snapshots
3738
+ WHERE tenant_id = $1 AND aggregate_type = 'agent_passport' AND aggregate_id = $2
3739
+ LIMIT 1
3740
+ `,
3741
+ [tenantId, normalizedAgentId]
3742
+ );
3743
+ return res.rows.length ? agentPassportSnapshotRowToRecord(res.rows[0]) : null;
3744
+ } catch (err) {
3745
+ if (err?.code !== "42P01") throw err;
3746
+ return store.agentPassports.get(makeScopedKey({ tenantId, id: normalizedAgentId })) ?? null;
3747
+ }
3748
+ };
3749
+
3750
+ store.putAgentPassport = async function putAgentPassport({ tenantId = DEFAULT_TENANT_ID, agentPassport } = {}) {
3751
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3752
+ if (!agentPassport || typeof agentPassport !== "object" || Array.isArray(agentPassport)) {
3753
+ throw new TypeError("agentPassport is required");
3754
+ }
3755
+ const agentId = typeof agentPassport.agentId === "string" ? agentPassport.agentId.trim() : "";
3756
+ if (!agentId) throw new TypeError("agentPassport.agentId is required");
3757
+ await store.commitTx({
3758
+ at: agentPassport.updatedAt ?? agentPassport.createdAt ?? new Date().toISOString(),
3759
+ ops: [{ kind: "AGENT_PASSPORT_UPSERT", tenantId, agentId, agentPassport: { ...agentPassport, tenantId, agentId } }]
3760
+ });
3761
+ return store.getAgentPassport({ tenantId, agentId });
3762
+ };
3763
+
2588
3764
  store.getAgentWallet = async function getAgentWallet({ tenantId = DEFAULT_TENANT_ID, agentId } = {}) {
2589
3765
  tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
2590
3766
  assertNonEmptyString(agentId, "agentId");
@@ -2758,6 +3934,88 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
2758
3934
  }
2759
3935
  };
2760
3936
 
3937
+ store.sumWalletPolicySpendCentsForDay = async function sumWalletPolicySpendCentsForDay({
3938
+ tenantId = DEFAULT_TENANT_ID,
3939
+ agentId,
3940
+ dayStartIso,
3941
+ dayEndIso
3942
+ } = {}) {
3943
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
3944
+ assertNonEmptyString(agentId, "agentId");
3945
+ assertNonEmptyString(dayStartIso, "dayStartIso");
3946
+ assertNonEmptyString(dayEndIso, "dayEndIso");
3947
+ const startMs = Date.parse(dayStartIso);
3948
+ const endMs = Date.parse(dayEndIso);
3949
+ if (!Number.isFinite(startMs)) throw new TypeError("dayStartIso must be an ISO date string");
3950
+ if (!Number.isFinite(endMs)) throw new TypeError("dayEndIso must be an ISO date string");
3951
+ if (!(endMs > startMs)) throw new TypeError("dayEndIso must be after dayStartIso");
3952
+
3953
+ let runSum = 0;
3954
+ try {
3955
+ const res = await pool.query(
3956
+ `
3957
+ SELECT COALESCE(SUM(amount_cents), 0)::bigint AS c
3958
+ FROM agent_run_settlements
3959
+ WHERE tenant_id = $1
3960
+ AND payer_agent_id = $2
3961
+ AND locked_at >= $3
3962
+ AND locked_at < $4
3963
+ `,
3964
+ [tenantId, String(agentId), String(dayStartIso), String(dayEndIso)]
3965
+ );
3966
+ const n = Number(res.rows[0]?.c ?? 0);
3967
+ runSum = Number.isSafeInteger(n) && n >= 0 ? n : 0;
3968
+ } catch (err) {
3969
+ if (err?.code !== "42P01") throw err;
3970
+ for (const row of store.agentRunSettlements.values()) {
3971
+ if (!row || typeof row !== "object") continue;
3972
+ if (normalizeTenantId(row.tenantId ?? DEFAULT_TENANT_ID) !== tenantId) continue;
3973
+ if (String(row.payerAgentId ?? "") !== String(agentId)) continue;
3974
+ const lockedAt = row.lockedAt ?? null;
3975
+ const lockedMs = typeof lockedAt === "string" ? Date.parse(lockedAt) : NaN;
3976
+ if (!Number.isFinite(lockedMs)) continue;
3977
+ if (lockedMs < startMs || lockedMs >= endMs) continue;
3978
+ const amountCents = Number(row.amountCents ?? 0);
3979
+ if (!Number.isSafeInteger(amountCents) || amountCents <= 0) continue;
3980
+ runSum += amountCents;
3981
+ }
3982
+ }
3983
+
3984
+ let holdSum = 0;
3985
+ try {
3986
+ const res = await pool.query(
3987
+ `
3988
+ SELECT COALESCE(SUM((snapshot_json->>'heldAmountCents')::bigint), 0)::bigint AS c
3989
+ FROM snapshots
3990
+ WHERE tenant_id = $1
3991
+ AND aggregate_type = 'tool_call_hold'
3992
+ AND snapshot_json->>'payerAgentId' = $2
3993
+ AND (snapshot_json->>'createdAt')::timestamptz >= $3::timestamptz
3994
+ AND (snapshot_json->>'createdAt')::timestamptz < $4::timestamptz
3995
+ `,
3996
+ [tenantId, String(agentId), String(dayStartIso), String(dayEndIso)]
3997
+ );
3998
+ const n = Number(res.rows[0]?.c ?? 0);
3999
+ holdSum = Number.isSafeInteger(n) && n >= 0 ? n : 0;
4000
+ } catch (err) {
4001
+ if (err?.code !== "42P01") throw err;
4002
+ for (const row of store.toolCallHolds.values()) {
4003
+ if (!row || typeof row !== "object") continue;
4004
+ if (normalizeTenantId(row.tenantId ?? DEFAULT_TENANT_ID) !== tenantId) continue;
4005
+ if (String(row.payerAgentId ?? "") !== String(agentId)) continue;
4006
+ const createdAt = row.createdAt ?? null;
4007
+ const createdMs = typeof createdAt === "string" ? Date.parse(createdAt) : NaN;
4008
+ if (!Number.isFinite(createdMs)) continue;
4009
+ if (createdMs < startMs || createdMs >= endMs) continue;
4010
+ const heldAmountCents = Number(row.heldAmountCents ?? 0);
4011
+ if (!Number.isSafeInteger(heldAmountCents) || heldAmountCents <= 0) continue;
4012
+ holdSum += heldAmountCents;
4013
+ }
4014
+ }
4015
+
4016
+ return runSum + holdSum;
4017
+ };
4018
+
2761
4019
  function arbitrationCaseSnapshotRowToRecord(row) {
2762
4020
  const arbitrationCase = row?.snapshot_json ?? null;
2763
4021
  if (!arbitrationCase || typeof arbitrationCase !== "object" || Array.isArray(arbitrationCase)) return null;
@@ -4734,6 +5992,58 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
4734
5992
  return res.rows.map((r) => r.artifact_json);
4735
5993
  };
4736
5994
 
5995
+ store.listReputationEvents = async function listReputationEvents({
5996
+ tenantId = DEFAULT_TENANT_ID,
5997
+ agentId,
5998
+ toolId = null,
5999
+ occurredAtGte = null,
6000
+ occurredAtLte = null,
6001
+ limit = 1000,
6002
+ offset = 0
6003
+ } = {}) {
6004
+ tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
6005
+ assertNonEmptyString(agentId, "agentId");
6006
+ if (toolId !== null && toolId !== undefined) assertNonEmptyString(toolId, "toolId");
6007
+ if (occurredAtGte !== null && occurredAtGte !== undefined) assertNonEmptyString(occurredAtGte, "occurredAtGte");
6008
+ if (occurredAtLte !== null && occurredAtLte !== undefined) assertNonEmptyString(occurredAtLte, "occurredAtLte");
6009
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new TypeError("limit must be a positive safe integer");
6010
+ if (!Number.isSafeInteger(offset) || offset < 0) throw new TypeError("offset must be a non-negative safe integer");
6011
+
6012
+ const params = [tenantId, String(agentId)];
6013
+ const where = ["idx.tenant_id = $1", "idx.subject_agent_id = $2"];
6014
+ if (toolId !== null && toolId !== undefined) {
6015
+ params.push(String(toolId));
6016
+ where.push(`idx.subject_tool_id = $${params.length}`);
6017
+ }
6018
+ if (occurredAtGte !== null && occurredAtGte !== undefined) {
6019
+ params.push(String(occurredAtGte));
6020
+ where.push(`idx.occurred_at >= $${params.length}::timestamptz`);
6021
+ }
6022
+ if (occurredAtLte !== null && occurredAtLte !== undefined) {
6023
+ params.push(String(occurredAtLte));
6024
+ where.push(`idx.occurred_at <= $${params.length}::timestamptz`);
6025
+ }
6026
+ const safeLimit = Math.min(5000, limit);
6027
+ params.push(safeLimit);
6028
+ params.push(offset);
6029
+
6030
+ const res = await pool.query(
6031
+ `
6032
+ SELECT a.artifact_json
6033
+ FROM reputation_event_index idx
6034
+ INNER JOIN artifacts a
6035
+ ON a.tenant_id = idx.tenant_id
6036
+ AND a.artifact_id = idx.artifact_id
6037
+ WHERE ${where.join(" AND ")}
6038
+ ORDER BY idx.occurred_at ASC, idx.artifact_id ASC
6039
+ LIMIT $${params.length - 1}
6040
+ OFFSET $${params.length}
6041
+ `,
6042
+ params
6043
+ );
6044
+ return res.rows.map((row) => row.artifact_json);
6045
+ };
6046
+
4737
6047
  store.createDelivery = async function createDelivery({ tenantId = DEFAULT_TENANT_ID, delivery }) {
4738
6048
  tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
4739
6049
  if (!delivery || typeof delivery !== "object") throw new TypeError("delivery is required");
@@ -5596,7 +6906,10 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
5596
6906
  `,
5597
6907
  [tenantId, periodStart, periodEnd]
5598
6908
  );
5599
- const jobIds = settledIdsRes.rows.map((r) => String(r.aggregate_id)).filter((id) => id && id.trim() !== "");
6909
+ const jobIds = settledIdsRes.rows
6910
+ .map((r) => String(r.aggregate_id))
6911
+ .filter((id) => id && id.trim() !== "")
6912
+ .sort((a, b) => a.localeCompare(b));
5600
6913
 
5601
6914
  const jobs = [];
5602
6915
  if (jobIds.length) {
@@ -5604,7 +6917,11 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
5604
6917
  "SELECT aggregate_id, snapshot_json FROM snapshots WHERE tenant_id = $1 AND aggregate_type = 'job' AND aggregate_id = ANY($2::text[])",
5605
6918
  [tenantId, jobIds]
5606
6919
  );
5607
- for (const r of snapRes.rows) {
6920
+ // Determinism: DB may return rows in arbitrary order for ANY($2), but artifacts must be hash-stable.
6921
+ const ordered = snapRes.rows
6922
+ .slice()
6923
+ .sort((a, b) => String(a.aggregate_id ?? "").localeCompare(String(b.aggregate_id ?? "")));
6924
+ for (const r of ordered) {
5608
6925
  jobs.push(r.snapshot_json);
5609
6926
  }
5610
6927
  }
@@ -6395,13 +7712,93 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
6395
7712
  return { processed, worker };
6396
7713
  }
6397
7714
 
7715
+ async function processNoopOutboxTopic({ topic, worker, maxMessages = Number.MAX_SAFE_INTEGER } = {}) {
7716
+ if (typeof topic !== "string" || topic.trim() === "") throw new TypeError("topic is required");
7717
+ if (typeof worker !== "string" || worker.trim() === "") throw new TypeError("worker is required");
7718
+ if (!Number.isSafeInteger(maxMessages) || maxMessages < 0) throw new TypeError("maxMessages must be a non-negative safe integer");
7719
+
7720
+ const processed = [];
7721
+
7722
+ // These topics are informational today (no worker consumes them in pg mode).
7723
+ // Drain them deterministically so ops / hosted-baseline checks don't wedge.
7724
+ while (processed.length < maxMessages) {
7725
+ const claimed = await store.claimOutbox({ topic, maxMessages: Math.min(1000, maxMessages - processed.length), worker });
7726
+ if (!claimed.length) break;
7727
+ const ids = claimed.map((r) => r.id);
7728
+ await store.markOutboxProcessed({ ids, lastError: "ok:noop" });
7729
+ for (const id of ids) processed.push({ id, status: "noop" });
7730
+ }
7731
+
7732
+ return { processed, worker };
7733
+ }
7734
+
6398
7735
  store.processOutbox = async function processOutbox({ maxMessages = 1000 } = {}) {
6399
7736
  const ledger = await processLedgerOutbox({ maxMessages });
6400
7737
  const notifications = await processNotificationsOutbox({ maxMessages });
6401
7738
  const correlations = await processCorrelationsOutbox({ maxMessages });
7739
+ const jobStatusChanged = await processNoopOutboxTopic({ topic: "JOB_STATUS_CHANGED", worker: "job_status_changed_v0", maxMessages });
7740
+ const jobSettled = await processNoopOutboxTopic({ topic: "JOB_SETTLED", worker: "job_settled_v0", maxMessages });
6402
7741
  const monthClose = await processMonthCloseOutbox({ maxMessages });
6403
7742
  const financePack = await processFinancePackOutbox({ maxMessages });
6404
- return { ledger, notifications, correlations, monthClose, financePack };
7743
+ return { ledger, notifications, correlations, jobStatusChanged, jobSettled, monthClose, financePack };
7744
+ };
7745
+
7746
+ // Read-only ops debugging helper (used by /ops/debug/outbox).
7747
+ // Intentionally narrow: surfaces enough to diagnose stuck/DLQ outbox without direct DB access.
7748
+ store.listOutboxDebug = async function listOutboxDebug({
7749
+ topic = null,
7750
+ tenantId = null,
7751
+ includeProcessed = false,
7752
+ state = null,
7753
+ limit = 50
7754
+ } = {}) {
7755
+ const safeLimit = Number.isSafeInteger(Number(limit)) ? Number(limit) : 50;
7756
+ if (safeLimit <= 0 || safeLimit > 500) throw new TypeError("limit must be a safe integer between 1 and 500");
7757
+ const t = typeof topic === "string" && topic.trim() ? topic.trim() : null;
7758
+ const tenant = typeof tenantId === "string" && tenantId.trim() ? normalizeTenantId(tenantId) : null;
7759
+ const normalizedState = typeof state === "string" && state.trim() ? state.trim().toLowerCase() : null;
7760
+ if (
7761
+ normalizedState !== null &&
7762
+ normalizedState !== "pending" &&
7763
+ normalizedState !== "processed" &&
7764
+ normalizedState !== "dlq" &&
7765
+ normalizedState !== "all"
7766
+ ) {
7767
+ throw new TypeError("state must be one of pending|processed|dlq|all");
7768
+ }
7769
+
7770
+ return await withTx({ statementTimeoutMs: workerStatementTimeoutMs }, async (client) => {
7771
+ const params = [];
7772
+ let where = `WHERE 1=1`;
7773
+ if (normalizedState === "pending") {
7774
+ where += ` AND processed_at IS NULL`;
7775
+ } else if (normalizedState === "processed") {
7776
+ where += ` AND processed_at IS NOT NULL AND (last_error IS NULL OR last_error NOT LIKE 'DLQ:%')`;
7777
+ } else if (normalizedState === "dlq") {
7778
+ where += ` AND processed_at IS NOT NULL AND last_error LIKE 'DLQ:%'`;
7779
+ } else if (normalizedState !== "all" && !includeProcessed) {
7780
+ // Backward compatibility when state is omitted.
7781
+ where += ` AND processed_at IS NULL`;
7782
+ }
7783
+ if (t) {
7784
+ params.push(String(t));
7785
+ where += ` AND topic = $${params.length}`;
7786
+ }
7787
+ if (tenant) {
7788
+ params.push(String(tenant));
7789
+ where += ` AND tenant_id = $${params.length}`;
7790
+ }
7791
+ params.push(safeLimit);
7792
+ const sql = `
7793
+ SELECT id, tenant_id, topic, aggregate_type, aggregate_id, attempts, claimed_at, processed_at, last_error, payload_json
7794
+ FROM outbox
7795
+ ${where}
7796
+ ORDER BY id DESC
7797
+ LIMIT $${params.length}
7798
+ `;
7799
+ const res = await client.query(sql, params);
7800
+ return res.rows;
7801
+ });
6405
7802
  };
6406
7803
 
6407
7804
  store.close = async function close() {