settld 0.1.1 → 0.1.5
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.
- package/README.md +61 -3
- package/SETTLD_VERSION +1 -1
- package/bin/settld-mcp +2 -0
- package/bin/settld.js +13 -0
- package/conformance/kernel-v0/README.md +7 -0
- package/conformance/kernel-v0/run.mjs +292 -4
- package/docs/ACCESS.md +57 -0
- package/docs/ADOPTION_CHECKLIST.md +44 -0
- package/docs/ALERTS.md +198 -0
- package/docs/ARCHITECTURE.md +69 -0
- package/docs/ARCHITECTURE_FOUNDER_GUIDE.md +284 -0
- package/docs/ARTIFACTS.md +60 -0
- package/docs/CERTIFICATION_CHECKLIST.md +33 -0
- package/docs/CIRCLE_SANDBOX_E2E.md +140 -0
- package/docs/CONFIG.md +297 -0
- package/docs/CONTRACTS_APIS.md +23 -0
- package/docs/DEPRECATION.md +31 -0
- package/docs/DOMAIN_MODEL.md +92 -0
- package/docs/EVENT_ENVELOPE.md +53 -0
- package/docs/FINANCE_PACK_FORMAT.md +53 -0
- package/docs/INCIDENT_TAXONOMY.md +30 -0
- package/docs/JOB_STATE_MACHINE.md +66 -0
- package/docs/KERNEL_COMPATIBLE.md +60 -0
- package/docs/KERNEL_V0.md +40 -0
- package/docs/KEY_ROTATION.md +80 -0
- package/docs/LEDGER.md +82 -0
- package/docs/LIVENESS.md +76 -0
- package/docs/MVP_BUILD_ORDER.md +36 -0
- package/docs/ONCALL_PLAYBOOK.md +39 -0
- package/docs/OPERATIONS_SIGNING.md +20 -0
- package/docs/OVERVIEW.md +190 -0
- package/docs/PERF_BASELINE.md +85 -0
- package/docs/PRD.md +77 -0
- package/docs/QUICKSTART_KERNEL_V0.md +96 -0
- package/docs/QUICKSTART_MCP.md +337 -0
- package/docs/QUICKSTART_MCP_HOSTS.md +143 -0
- package/docs/QUICKSTART_PRODUCE.md +61 -0
- package/docs/QUICKSTART_RELEASE_VERIFY.md +39 -0
- package/docs/QUICKSTART_SDK.md +125 -0
- package/docs/QUICKSTART_SDK_PYTHON.md +111 -0
- package/docs/QUICKSTART_VERIFY.md +54 -0
- package/docs/QUICKSTART_X402_GATEWAY.md +317 -0
- package/docs/README.md +15 -0
- package/docs/RELEASE_CHECKLIST.md +156 -0
- package/docs/RELEASING.md +81 -0
- package/docs/REPO_SETTINGS.md +37 -0
- package/docs/RUNBOOK.md +86 -0
- package/docs/SKILLS.md +42 -0
- package/docs/SKILL_BUNDLE_FORMAT.md +48 -0
- package/docs/SLO.md +70 -0
- package/docs/SUMMARY.md +16 -0
- package/docs/SUPPORT.md +31 -0
- package/docs/THREAT_MODEL.md +36 -0
- package/docs/TRUST.md +59 -0
- package/docs/WORKFLOW.md +35 -0
- package/docs/X402_BATCH_SETTLEMENT.md +126 -0
- package/docs/blog/2026-02-14-your-ai-agent-just-spent-500-where-is-the-receipt.md +73 -0
- package/docs/examples/x402-provider-payout-registry.example.json +14 -0
- package/docs/gitbook/README.md +52 -0
- package/docs/gitbook/SETUP.md +25 -0
- package/docs/gitbook/SUMMARY.md +15 -0
- package/docs/gitbook/api-reference.md +73 -0
- package/docs/gitbook/closepacks.md +55 -0
- package/docs/gitbook/conformance.md +59 -0
- package/docs/gitbook/core-primitives.md +85 -0
- package/docs/gitbook/dispute-lifecycle.md +33 -0
- package/docs/gitbook/faq.md +21 -0
- package/docs/gitbook/guides.md +49 -0
- package/docs/gitbook/operations-runbook.md +36 -0
- package/docs/gitbook/quickstart.md +104 -0
- package/docs/gitbook/replay-and-audit.md +30 -0
- package/docs/gitbook/sdk-reference.md +35 -0
- package/docs/gitbook/security-model.md +58 -0
- package/docs/integrations/README.md +14 -0
- package/docs/integrations/github-actions-verify.yml +31 -0
- package/docs/integrations/github-actions.md +34 -0
- package/docs/integrations/openclaw/CLAWHUB_PUBLISH_CHECKLIST.md +65 -0
- package/docs/integrations/openclaw/settld-mcp-skill/SKILL.md +69 -0
- package/docs/integrations/openclaw/settld-mcp-skill/mcp-server.example.json +12 -0
- package/docs/kernel-compatible/capabilities.json +36 -0
- package/docs/marketing/agent-commerce-substrate.md +78 -0
- package/docs/marketing/hn-repost-2026-02-17.md +102 -0
- package/docs/marketing/show-hn-post.md +45 -0
- package/docs/ops/ARTIFACT_VERIFICATION_STATUS.md +43 -0
- package/docs/ops/BILLING_WEBHOOK_REPLAY.md +105 -0
- package/docs/ops/CI_FLAKE_BUDGET.md +31 -0
- package/docs/ops/GO_LIVE_GATE_S13.md +27 -0
- package/docs/ops/HOSTED_BASELINE_R2.md +129 -0
- package/docs/ops/KERNEL_V0_SHIP_GATE.md +67 -0
- package/docs/ops/LIGHTHOUSE_PRODUCTION_CLOSE.md +51 -0
- package/docs/ops/MCP_COMPATIBILITY_MATRIX.md +28 -0
- package/docs/ops/MINIMUM_PRODUCTION_TOPOLOGY.md +89 -0
- package/docs/ops/P0_BACKEND_PROGRESS.md +150 -0
- package/docs/ops/PAYMENTS_ALPHA_R5.md +105 -0
- package/docs/ops/PILOT_ONBOARDING_RUNBOOK.md +112 -0
- package/docs/ops/PRODUCTION_DEPLOYMENT_CHECKLIST.md +103 -0
- package/docs/ops/R1_SLOS.md +66 -0
- package/docs/ops/RELEASE_SIGNING_INCIDENT.md +58 -0
- package/docs/ops/SELF_SERVE_LAUNCH_AUTOMATION.md +89 -0
- package/docs/ops/THROUGHPUT_DRILL_10X.md +48 -0
- package/docs/ops/TRUST_CONFIG_WIZARD.md +47 -0
- package/docs/ops/X402_PILOT_WEEKLY_METRICS.md +76 -0
- package/docs/ops/tool-call-disputes-holdback.md +52 -0
- package/docs/pilot-kit/PILOT_PACKAGE_SCORECARD_X402.md +46 -0
- package/docs/pilot-kit/README.md +29 -0
- package/docs/pilot-kit/architecture-one-pager.md +48 -0
- package/docs/pilot-kit/buyer-email.txt +19 -0
- package/docs/pilot-kit/buyer-one-pager.md +31 -0
- package/docs/pilot-kit/gtm-pilot-playbook.md +182 -0
- package/docs/pilot-kit/offline-verify.md +33 -0
- package/docs/pilot-kit/procurement-one-pager.md +50 -0
- package/docs/pilot-kit/rfp-clause.md +46 -0
- package/docs/pilot-kit/roi-calculator-template.csv +2 -0
- package/docs/pilot-kit/security-qa.md +153 -0
- package/docs/pilot-kit/security-summary.md +35 -0
- package/docs/plans/2026-02-13-mcp-spike-design.md +113 -0
- package/docs/spec/AcceptanceCriteria.v1.md +17 -0
- package/docs/spec/AcceptanceEvaluation.v1.md +10 -0
- package/docs/spec/AgentEvent.v1.md +47 -0
- package/docs/spec/AgentIdentity.v1.md +62 -0
- package/docs/spec/AgentPassport.v1.md +95 -0
- package/docs/spec/AgentReputation.v1.md +59 -0
- package/docs/spec/AgentReputation.v2.md +52 -0
- package/docs/spec/AgentRun.v1.md +47 -0
- package/docs/spec/AgentRunSettlement.v1.md +52 -0
- package/docs/spec/AgentWallet.v1.md +43 -0
- package/docs/spec/AgreementDelegation.v1.md +109 -0
- package/docs/spec/ArbitrationCase.v1.md +67 -0
- package/docs/spec/ArbitrationVerdict.v1.md +60 -0
- package/docs/spec/BundleHeadAttestation.v1.md +32 -0
- package/docs/spec/CANONICAL_JSON.md +31 -0
- package/docs/spec/CRYPTOGRAPHY.md +61 -0
- package/docs/spec/ClosePack.v1.md +49 -0
- package/docs/spec/ClosePackManifest.v1.md +24 -0
- package/docs/spec/DelegationGrant.v1.md +90 -0
- package/docs/spec/DisputeOpenEnvelope.v1.md +43 -0
- package/docs/spec/ERRORS.md +76 -0
- package/docs/spec/ESCROW_NETTING_INVARIANTS.md +71 -0
- package/docs/spec/EvidenceIndex.v1.md +20 -0
- package/docs/spec/ExecutionIntent.v1.md +90 -0
- package/docs/spec/FinancePackBundleManifest.v1.md +24 -0
- package/docs/spec/FundingHold.v1.md +60 -0
- package/docs/spec/GovernancePolicy.v1.md +34 -0
- package/docs/spec/GovernancePolicy.v2.md +30 -0
- package/docs/spec/INVARIANTS.md +389 -0
- package/docs/spec/InteractionDirectionMatrix.v1.md +30 -0
- package/docs/spec/InvoiceBundleManifest.v1.md +24 -0
- package/docs/spec/InvoiceClaim.v1.md +11 -0
- package/docs/spec/MONEY_RAIL_STATE_MACHINE.md +58 -0
- package/docs/spec/MarketplaceAcceptance.v2.md +46 -0
- package/docs/spec/MarketplaceOffer.v2.md +54 -0
- package/docs/spec/MeteringReport.v1.md +18 -0
- package/docs/spec/PRODUCER_ERRORS.md +42 -0
- package/docs/spec/PricingMatrix.v1.md +20 -0
- package/docs/spec/PricingMatrixSignatures.v1.md +30 -0
- package/docs/spec/PricingMatrixSignatures.v2.md +29 -0
- package/docs/spec/ProduceCliOutput.v1.md +46 -0
- package/docs/spec/ProofBundleManifest.v1.md +24 -0
- package/docs/spec/README.md +104 -0
- package/docs/spec/REFERENCE_IMPLEMENTATIONS.md +29 -0
- package/docs/spec/REFERENCE_VERIFIER_BEHAVIOR.md +68 -0
- package/docs/spec/REMOTE_SIGNER.md +66 -0
- package/docs/spec/ReleaseIndex.v1.md +32 -0
- package/docs/spec/ReleaseIndexSignatures.v1.md +17 -0
- package/docs/spec/ReleaseTrust.v1.md +13 -0
- package/docs/spec/ReleaseTrust.v2.md +26 -0
- package/docs/spec/RemoteSignerRequest.v1.md +21 -0
- package/docs/spec/RemoteSignerResponse.v1.md +16 -0
- package/docs/spec/ReputationEvent.v1.md +63 -0
- package/docs/spec/RevocationList.v1.md +28 -0
- package/docs/spec/SIGNER_PROVIDER_PLUGIN.md +32 -0
- package/docs/spec/STRICTNESS.md +68 -0
- package/docs/spec/SUPPLY_CHAIN.md +33 -0
- package/docs/spec/SettlementAdjustment.v1.md +45 -0
- package/docs/spec/SettlementDecisionRecord.v1.md +48 -0
- package/docs/spec/SettlementDecisionRecord.v2.md +51 -0
- package/docs/spec/SettlementDecisionReport.v1.md +44 -0
- package/docs/spec/SettlementKernel.v1.md +59 -0
- package/docs/spec/SettlementReceipt.v1.md +63 -0
- package/docs/spec/SlaDefinition.v1.md +24 -0
- package/docs/spec/SlaEvaluation.v1.md +12 -0
- package/docs/spec/THREAT_MODEL.md +113 -0
- package/docs/spec/TOOL_PROVENANCE.md +30 -0
- package/docs/spec/TRUST_ANCHORS.md +84 -0
- package/docs/spec/TenantSettings.v1.md +90 -0
- package/docs/spec/TenantSettings.v2.md +99 -0
- package/docs/spec/TimestampProof.v1.md +25 -0
- package/docs/spec/ToolCallAgreement.v1.md +34 -0
- package/docs/spec/ToolCallEvidence.v1.md +47 -0
- package/docs/spec/ToolManifest.v1.md +47 -0
- package/docs/spec/VERIFIER_ENVIRONMENT.md +38 -0
- package/docs/spec/VERSIONING.md +107 -0
- package/docs/spec/VerificationReport.v1.md +50 -0
- package/docs/spec/VerifyAboutOutput.v1.md +10 -0
- package/docs/spec/VerifyCliOutput.v1.md +28 -0
- package/docs/spec/WARNINGS.md +83 -0
- package/docs/spec/error-codes.v1.txt +285 -0
- package/docs/spec/examples/agreement_delegation_v1.example.json +21 -0
- package/docs/spec/examples/arbitration_case_v1.example.json +26 -0
- package/docs/spec/examples/arbitration_verdict_v1.example.json +32 -0
- package/docs/spec/examples/dispute_open_envelope_v1.example.json +18 -0
- package/docs/spec/examples/produce_cli_output_v1.example.json +32 -0
- package/docs/spec/examples/release_index_signature_v1.example.json +9 -0
- package/docs/spec/examples/release_index_signatures_v1.example.json +14 -0
- package/docs/spec/examples/release_index_v1.example.json +15 -0
- package/docs/spec/examples/release_trust_v1.example.json +7 -0
- package/docs/spec/examples/release_trust_v2.example.json +22 -0
- package/docs/spec/examples/remote_signer_request_v1.example.json +18 -0
- package/docs/spec/examples/remote_signer_response_v1.example.json +8 -0
- package/docs/spec/examples/reputation_event_v1.example.json +29 -0
- package/docs/spec/examples/verification_report_v1.example.json +24 -0
- package/docs/spec/examples/verify_about_output_v1.example.json +29 -0
- package/docs/spec/examples/verify_cli_output_v1.example.json +13 -0
- package/docs/spec/legacy/MarketplaceAcceptance.v1.md +48 -0
- package/docs/spec/legacy/MarketplaceOffer.v1.md +56 -0
- package/docs/spec/legacy/schemas/MarketplaceAcceptance.v1.schema.json +53 -0
- package/docs/spec/legacy/schemas/MarketplaceOffer.v1.schema.json +61 -0
- package/docs/spec/producer-error-codes.v1.txt +14 -0
- package/docs/spec/schemas/AcceptanceCriteria.v1.schema.json +24 -0
- package/docs/spec/schemas/AcceptanceEvaluation.v1.schema.json +26 -0
- package/docs/spec/schemas/AgentEvent.v1.schema.json +49 -0
- package/docs/spec/schemas/AgentIdentity.v1.schema.json +129 -0
- package/docs/spec/schemas/AgentPassport.v1.schema.json +112 -0
- package/docs/spec/schemas/AgentReputation.v1.schema.json +151 -0
- package/docs/spec/schemas/AgentReputation.v2.schema.json +120 -0
- package/docs/spec/schemas/AgentRun.v1.schema.json +71 -0
- package/docs/spec/schemas/AgentRunSettlement.v1.schema.json +75 -0
- package/docs/spec/schemas/AgentWallet.v1.schema.json +54 -0
- package/docs/spec/schemas/AgreementDelegation.v1.schema.json +50 -0
- package/docs/spec/schemas/ArbitrationCase.v1.schema.json +133 -0
- package/docs/spec/schemas/ArbitrationVerdict.v1.schema.json +149 -0
- package/docs/spec/schemas/BundleHeadAttestation.v1.schema.json +21 -0
- package/docs/spec/schemas/ClosePackManifest.v1.schema.json +38 -0
- package/docs/spec/schemas/DelegationGrant.v1.schema.json +102 -0
- package/docs/spec/schemas/DisputeOpenEnvelope.v1.schema.json +78 -0
- package/docs/spec/schemas/EvidenceIndex.v1.schema.json +41 -0
- package/docs/spec/schemas/ExecutionIntent.v1.schema.json +85 -0
- package/docs/spec/schemas/FinancePackBundleManifest.v1.schema.json +38 -0
- package/docs/spec/schemas/FundingHold.v1.schema.json +46 -0
- package/docs/spec/schemas/GovernancePolicy.v1.schema.json +45 -0
- package/docs/spec/schemas/GovernancePolicy.v2.schema.json +70 -0
- package/docs/spec/schemas/InteractionDirectionMatrix.v1.schema.json +43 -0
- package/docs/spec/schemas/InvoiceBundleManifest.v1.schema.json +38 -0
- package/docs/spec/schemas/InvoiceClaim.v1.schema.json +39 -0
- package/docs/spec/schemas/MarketplaceAcceptance.v2.schema.json +53 -0
- package/docs/spec/schemas/MarketplaceOffer.v2.schema.json +61 -0
- package/docs/spec/schemas/MeteringReport.v1.schema.json +45 -0
- package/docs/spec/schemas/PricingMatrix.v1.schema.json +24 -0
- package/docs/spec/schemas/PricingMatrixSignatures.v1.schema.json +24 -0
- package/docs/spec/schemas/PricingMatrixSignatures.v2.schema.json +24 -0
- package/docs/spec/schemas/ProduceCliOutput.v1.schema.json +107 -0
- package/docs/spec/schemas/ProofBundleManifest.v1.schema.json +37 -0
- package/docs/spec/schemas/PublicKeys.v1.schema.json +33 -0
- package/docs/spec/schemas/ReleaseIndex.v1.schema.json +45 -0
- package/docs/spec/schemas/ReleaseIndexSignature.v1.schema.json +16 -0
- package/docs/spec/schemas/ReleaseIndexSignatures.v1.schema.json +16 -0
- package/docs/spec/schemas/ReleaseTrust.v1.schema.json +15 -0
- package/docs/spec/schemas/ReleaseTrust.v2.schema.json +37 -0
- package/docs/spec/schemas/RemoteSignerPublicKeyResponse.v1.schema.json +14 -0
- package/docs/spec/schemas/RemoteSignerRequest.v1.schema.json +24 -0
- package/docs/spec/schemas/RemoteSignerResponse.v1.schema.json +10 -0
- package/docs/spec/schemas/RemoteSignerSignRequest.v1.schema.json +27 -0
- package/docs/spec/schemas/RemoteSignerSignResponse.v1.schema.json +16 -0
- package/docs/spec/schemas/ReputationEvent.v1.schema.json +164 -0
- package/docs/spec/schemas/RevocationList.v1.schema.json +51 -0
- package/docs/spec/schemas/SettlementAdjustment.v1.schema.json +44 -0
- package/docs/spec/schemas/SettlementDecisionRecord.v1.schema.json +66 -0
- package/docs/spec/schemas/SettlementDecisionRecord.v2.schema.json +148 -0
- package/docs/spec/schemas/SettlementDecisionReport.v1.schema.json +61 -0
- package/docs/spec/schemas/SettlementReceipt.v1.schema.json +135 -0
- package/docs/spec/schemas/SlaDefinition.v1.schema.json +33 -0
- package/docs/spec/schemas/SlaEvaluation.v1.schema.json +26 -0
- package/docs/spec/schemas/TenantSettings.v1.schema.json +90 -0
- package/docs/spec/schemas/TenantSettings.v2.schema.json +161 -0
- package/docs/spec/schemas/TimestampProof.v1.schema.json +17 -0
- package/docs/spec/schemas/ToolCallAgreement.v1.schema.json +34 -0
- package/docs/spec/schemas/ToolCallEvidence.v1.schema.json +45 -0
- package/docs/spec/schemas/ToolManifest.v1.schema.json +54 -0
- package/docs/spec/schemas/VerificationReport.v1.schema.json +83 -0
- package/docs/spec/schemas/VerifyAboutOutput.v1.schema.json +54 -0
- package/docs/spec/schemas/VerifyCliOutput.v1.schema.json +75 -0
- package/docs/spec/schemas/VerifyReleaseOutput.v1.schema.json +47 -0
- package/docs/spec/x402-error-codes.v1.txt +21 -0
- package/docs/templates/buyer-email.txt +18 -0
- package/docs/templates/buyer-one-pager.md +24 -0
- package/package.json +40 -6
- package/scripts/acceptance/full-stack.mjs +734 -0
- package/scripts/acceptance/full-stack.sh +99 -0
- package/scripts/audit/build-audit-packet.mjs +242 -0
- package/scripts/backup-pg.sh +45 -0
- package/scripts/backup-restore/README.md +18 -0
- package/scripts/backup-restore/capture-state.mjs +130 -0
- package/scripts/backup-restore/client.mjs +97 -0
- package/scripts/backup-restore/seed-workload.mjs +235 -0
- package/scripts/backup-restore/verify-state.mjs +139 -0
- package/scripts/backup-restore-test.sh +217 -0
- package/scripts/chaos.js +221 -0
- package/scripts/ci/build-launch-cutover-packet.mjs +148 -0
- package/scripts/ci/build-self-serve-benchmark-report.mjs +122 -0
- package/scripts/ci/changelog-guard.mjs +145 -0
- package/scripts/ci/check-kernel-v0-launch-gate.mjs +233 -0
- package/scripts/ci/check-secret-hygiene.mjs +78 -0
- package/scripts/ci/check-version-consistency.mjs +42 -0
- package/scripts/ci/cli-pack-smoke.mjs +160 -0
- package/scripts/ci/flake-budget-guard.mjs +68 -0
- package/scripts/ci/generate-error-codes.mjs +54 -0
- package/scripts/ci/lib/lighthouse-tracker.mjs +90 -0
- package/scripts/ci/lib/self-serve-launch-gate.mjs +89 -0
- package/scripts/ci/npm-pack-smoke.mjs +454 -0
- package/scripts/ci/run-10x-throughput-drill.mjs +246 -0
- package/scripts/ci/run-10x-throughput-incident-rehearsal.mjs +325 -0
- package/scripts/ci/run-arbitration-workspace-browser-e2e.sh +22 -0
- package/scripts/ci/run-circle-sandbox-smoke.mjs +237 -0
- package/scripts/ci/run-go-live-gate.mjs +150 -0
- package/scripts/ci/run-kernel-v0-ship-gate.mjs +97 -0
- package/scripts/ci/run-mcp-host-smoke.mjs +275 -0
- package/scripts/ci/run-self-serve-launch-gate.mjs +56 -0
- package/scripts/ci/runtime-import-smoke.mjs +58 -0
- package/scripts/ci/update-lighthouse-tracker.mjs +112 -0
- package/scripts/closepack/lib.mjs +286 -0
- package/scripts/collect-debug.sh +263 -0
- package/scripts/demo/compositional-settlement-3hop.mjs +237 -0
- package/scripts/demo/delivery-robot/export-ui-fixture.mjs +188 -0
- package/scripts/demo/delivery-robot/generate.mjs +377 -0
- package/scripts/demo/kernel-agent-goes-shopping.mjs +202 -0
- package/scripts/demo/magic-link-first-green.mjs +118 -0
- package/scripts/demo/magic-link-kind-smoke.mjs +577 -0
- package/scripts/demo/mcp-paid-exa.mjs +1110 -0
- package/scripts/dev/billing-doctor.sh +145 -0
- package/scripts/dev/billing-smoke-prod.sh +219 -0
- package/scripts/dev/billing-webhook-replay.sh +161 -0
- package/scripts/dev/env.dev.example +29 -0
- package/scripts/dev/env.sh +37 -0
- package/scripts/dev/new-sdk-key.sh +81 -0
- package/scripts/dev/sdk-first-run.sh +21 -0
- package/scripts/dev/smoke-x402-gateway.sh +115 -0
- package/scripts/dev/start-api.sh +24 -0
- package/scripts/examples/produce-and-verify-jobproof.mjs +191 -0
- package/scripts/examples/sdk-first-paid-rfq.py +105 -0
- package/scripts/examples/sdk-first-verified-run.mjs +85 -0
- package/scripts/examples/sdk-first-verified-run.py +99 -0
- package/scripts/examples/sdk-tenant-analytics.mjs +103 -0
- package/scripts/examples/sdk-tenant-analytics.py +118 -0
- package/scripts/finance-pack/bundle.mjs +284 -0
- package/scripts/fixtures/generate-bundle-fixtures.mjs +877 -0
- package/scripts/governance/export.mjs +169 -0
- package/scripts/load/delivery-stress.k6.js +183 -0
- package/scripts/load/ingest-burst.k6.js +236 -0
- package/scripts/load/run-delivery-load.js +66 -0
- package/scripts/load/webhook-receiver.js +131 -0
- package/scripts/magic-link/migrate-run-records-to-db.mjs +35 -0
- package/scripts/mcp/probe.mjs +238 -0
- package/scripts/mcp/settld-mcp-http-gateway.mjs +178 -0
- package/scripts/mcp/settld-mcp-server.mjs +1201 -0
- package/scripts/openapi/write.mjs +13 -0
- package/scripts/ops/bootstrap-tenant-conformance.mjs +185 -0
- package/scripts/ops/build-x402-pilot-reliability-report.mjs +489 -0
- package/scripts/ops/check-x402-receipt-sample.mjs +181 -0
- package/scripts/ops/design-partner-run-packet.mjs +466 -0
- package/scripts/ops/hosted-baseline-evidence.mjs +681 -0
- package/scripts/ops/money-rails-chargeback-evidence.mjs +509 -0
- package/scripts/ops/money-rails-reconcile-evidence.mjs +180 -0
- package/scripts/ops/p0-seed-money-rail-operation.mjs +432 -0
- package/scripts/pilot/finance-pack.mjs +495 -0
- package/scripts/pilot/fixtures/robot-keypair.json +4 -0
- package/scripts/pilot/fixtures/server-signer.json +4 -0
- package/scripts/proof-bundle/job.mjs +109 -0
- package/scripts/proof-bundle/lib.mjs +92 -0
- package/scripts/proof-bundle/month.mjs +103 -0
- package/scripts/provider/conformance-run.mjs +159 -0
- package/scripts/provider/keys-generate.mjs +135 -0
- package/scripts/provider/publish.mjs +420 -0
- package/scripts/quickstart/x402.mjs +334 -0
- package/scripts/release/build-artifacts.mjs +181 -0
- package/scripts/release/generate-release-index.mjs +112 -0
- package/scripts/release/release-index-lib.mjs +232 -0
- package/scripts/release/sign-release-index.mjs +85 -0
- package/scripts/release/validate-release-assets.mjs +170 -0
- package/scripts/release/verify-release.mjs +261 -0
- package/scripts/restore-pg.sh +34 -0
- package/scripts/scaffold/create-settld-paid-tool.mjs +19 -0
- package/scripts/sdk/smoke-python.py +30 -0
- package/scripts/sdk/smoke.mjs +16 -0
- package/scripts/settlement/x402-batch-worker.mjs +1091 -0
- package/scripts/slo/check.mjs +178 -0
- package/scripts/smoke/k8s-smoke.mjs +214 -0
- package/scripts/spec/generate-protocol-vectors.mjs +931 -0
- package/scripts/test/check-no-generated-artifacts.sh +12 -0
- package/scripts/test/run.sh +45 -0
- package/scripts/trust/validate-trust-file.mjs +57 -0
- package/scripts/trust-config/rotate-settld-pay.mjs +277 -0
- package/scripts/trust-config/wizard.mjs +161 -0
- package/scripts/vendor-contract-test-lib.mjs +182 -0
- package/scripts/vendor-contract-test.mjs +55 -0
- package/scripts/vercel/build-mkdocs.sh +9 -0
- package/scripts/vercel/ignore-mkdocs.sh +25 -0
- package/scripts/vercel/install-mkdocs.sh +6 -0
- package/scripts/verify-pg.js +217 -0
- package/scripts/x402/receipt-verify.mjs +289 -0
- package/services/finance-sink/src/dedupe-store.js +29 -6
- package/services/receiver/src/dedupe-store.js +29 -5
- package/services/x402-gateway/Dockerfile +13 -0
- package/services/x402-gateway/README.md +58 -0
- package/services/x402-gateway/examples/upstream-mock.js +337 -0
- package/services/x402-gateway/src/server.js +947 -0
- package/src/api/app.js +32517 -16877
- package/src/api/maintenance.js +70 -0
- package/src/api/openapi.js +1130 -17
- package/src/api/persistence.js +272 -0
- package/src/api/server.js +81 -5
- package/src/api/store.js +1248 -6
- package/src/api/workers/deliveries.js +99 -4
- package/src/api/workers/insolvency-sweep.js +159 -0
- package/src/core/agent-card.js +69 -0
- package/src/core/agent-wallets.js +97 -0
- package/src/core/agreement-delegation.js +549 -0
- package/src/core/billing-plans.js +40 -6
- package/src/core/circle-reserve-adapter.js +845 -0
- package/src/core/maintenance-locks.js +1 -0
- package/src/core/paid-tool-manifest.js +318 -0
- package/src/core/provider-publish-conformance.js +525 -0
- package/src/core/provider-publish-proof.js +396 -0
- package/src/core/provider-quote-signature.js +170 -0
- package/src/core/settld-keys.js +112 -0
- package/src/core/settld-pay-token.js +344 -0
- package/src/core/settlement-kernel.js +213 -2
- package/src/core/settlement-verifier.js +335 -0
- package/src/core/tool-call-agreement.js +112 -0
- package/src/core/tool-call-evidence.js +144 -0
- package/src/core/tool-provider-signature.js +98 -0
- package/src/core/x402-escalation-override.js +258 -0
- package/src/core/x402-gate.js +118 -0
- package/src/core/x402-provider-refund-decision.js +220 -0
- package/src/core/x402-receipt-verifier.js +708 -0
- package/src/core/x402-reversal-command.js +251 -0
- package/src/core/x402-wallet-issuer-decision.js +252 -0
- package/src/core/zk-verifier.js +300 -0
- package/src/db/migrations/029_reputation_event_index.sql +54 -0
- package/src/db/migrations/030_artifacts_source_event_unique_job_only.sql +15 -0
- package/src/db/pg.js +18 -7
- package/src/db/store-pg.js +838 -72
package/src/db/store-pg.js
CHANGED
|
@@ -155,6 +155,24 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
155
155
|
store.agentRuns.clear();
|
|
156
156
|
if (!(store.arbitrationCases instanceof Map)) store.arbitrationCases = new Map();
|
|
157
157
|
store.arbitrationCases.clear();
|
|
158
|
+
if (!(store.agreementDelegations instanceof Map)) store.agreementDelegations = new Map();
|
|
159
|
+
store.agreementDelegations.clear();
|
|
160
|
+
if (!(store.x402Gates instanceof Map)) store.x402Gates = new Map();
|
|
161
|
+
store.x402Gates.clear();
|
|
162
|
+
if (!(store.x402AgentLifecycles instanceof Map)) store.x402AgentLifecycles = new Map();
|
|
163
|
+
store.x402AgentLifecycles.clear();
|
|
164
|
+
if (!(store.x402Receipts instanceof Map)) store.x402Receipts = new Map();
|
|
165
|
+
store.x402Receipts.clear();
|
|
166
|
+
if (!(store.x402WalletPolicies instanceof Map)) store.x402WalletPolicies = new Map();
|
|
167
|
+
store.x402WalletPolicies.clear();
|
|
168
|
+
if (!(store.x402ZkVerificationKeys instanceof Map)) store.x402ZkVerificationKeys = new Map();
|
|
169
|
+
store.x402ZkVerificationKeys.clear();
|
|
170
|
+
if (!(store.x402ReversalEvents instanceof Map)) store.x402ReversalEvents = new Map();
|
|
171
|
+
store.x402ReversalEvents.clear();
|
|
172
|
+
if (!(store.x402ReversalNonceUsage instanceof Map)) store.x402ReversalNonceUsage = new Map();
|
|
173
|
+
store.x402ReversalNonceUsage.clear();
|
|
174
|
+
if (!(store.x402ReversalCommandUsage instanceof Map)) store.x402ReversalCommandUsage = new Map();
|
|
175
|
+
store.x402ReversalCommandUsage.clear();
|
|
158
176
|
if (!(store.toolCallHolds instanceof Map)) store.toolCallHolds = new Map();
|
|
159
177
|
store.toolCallHolds.clear();
|
|
160
178
|
if (!(store.settlementAdjustments instanceof Map)) store.settlementAdjustments = new Map();
|
|
@@ -180,6 +198,84 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
180
198
|
caseId: snap?.caseId ?? String(id)
|
|
181
199
|
});
|
|
182
200
|
}
|
|
201
|
+
if (type === "agreement_delegation") {
|
|
202
|
+
store.agreementDelegations.set(key, {
|
|
203
|
+
...snap,
|
|
204
|
+
tenantId: snap?.tenantId ?? tenantId,
|
|
205
|
+
delegationId: snap?.delegationId ?? String(id)
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (type === "x402_gate") {
|
|
209
|
+
store.x402Gates.set(key, {
|
|
210
|
+
...snap,
|
|
211
|
+
tenantId: snap?.tenantId ?? tenantId,
|
|
212
|
+
gateId: snap?.gateId ?? String(id)
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (type === "x402_agent_lifecycle") {
|
|
216
|
+
store.x402AgentLifecycles.set(key, {
|
|
217
|
+
...snap,
|
|
218
|
+
tenantId: snap?.tenantId ?? tenantId,
|
|
219
|
+
agentId: snap?.agentId ?? String(id)
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (type === "x402_receipt") {
|
|
223
|
+
store.x402Receipts.set(key, {
|
|
224
|
+
...snap,
|
|
225
|
+
tenantId: snap?.tenantId ?? tenantId,
|
|
226
|
+
receiptId: snap?.receiptId ?? String(id),
|
|
227
|
+
reversal: null,
|
|
228
|
+
reversalEvents: []
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (type === "x402_wallet_policy") {
|
|
232
|
+
const sponsorWalletRef = typeof snap?.sponsorWalletRef === "string" ? snap.sponsorWalletRef : null;
|
|
233
|
+
const policyRef = typeof snap?.policyRef === "string" ? snap.policyRef : null;
|
|
234
|
+
const policyVersion = parseSafeIntegerOrNull(snap?.policyVersion);
|
|
235
|
+
if (sponsorWalletRef && policyRef && policyVersion !== null && policyVersion > 0) {
|
|
236
|
+
store.x402WalletPolicies.set(makeScopedKey({ tenantId, id: `${sponsorWalletRef}::${policyRef}::${policyVersion}` }), {
|
|
237
|
+
...snap,
|
|
238
|
+
tenantId: snap?.tenantId ?? tenantId,
|
|
239
|
+
sponsorWalletRef,
|
|
240
|
+
policyRef,
|
|
241
|
+
policyVersion
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (type === "x402_zk_verification_key") {
|
|
246
|
+
store.x402ZkVerificationKeys.set(key, {
|
|
247
|
+
...snap,
|
|
248
|
+
tenantId: snap?.tenantId ?? tenantId,
|
|
249
|
+
verificationKeyId: snap?.verificationKeyId ?? String(id)
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (type === "x402_reversal_event") {
|
|
253
|
+
store.x402ReversalEvents.set(key, {
|
|
254
|
+
...snap,
|
|
255
|
+
tenantId: snap?.tenantId ?? tenantId,
|
|
256
|
+
eventId: snap?.eventId ?? String(id)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (type === "x402_reversal_nonce") {
|
|
260
|
+
const sponsorRef = typeof snap?.sponsorRef === "string" ? snap.sponsorRef : null;
|
|
261
|
+
const nonce = typeof snap?.nonce === "string" ? snap.nonce : null;
|
|
262
|
+
if (sponsorRef && nonce) {
|
|
263
|
+
const nonceKey = `${tenantId}\n${sponsorRef}\n${nonce}`;
|
|
264
|
+
store.x402ReversalNonceUsage.set(nonceKey, {
|
|
265
|
+
...snap,
|
|
266
|
+
tenantId,
|
|
267
|
+
sponsorRef,
|
|
268
|
+
nonce
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (type === "x402_reversal_command") {
|
|
273
|
+
store.x402ReversalCommandUsage.set(key, {
|
|
274
|
+
...snap,
|
|
275
|
+
tenantId: snap?.tenantId ?? tenantId,
|
|
276
|
+
commandId: snap?.commandId ?? String(id)
|
|
277
|
+
});
|
|
278
|
+
}
|
|
183
279
|
if (type === "tool_call_hold") {
|
|
184
280
|
store.toolCallHolds.set(key, {
|
|
185
281
|
...snap,
|
|
@@ -1149,6 +1245,341 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
1149
1245
|
);
|
|
1150
1246
|
}
|
|
1151
1247
|
|
|
1248
|
+
async function persistAgreementDelegation(client, { tenantId, delegationId, delegation }) {
|
|
1249
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1250
|
+
if (!delegation || typeof delegation !== "object" || Array.isArray(delegation)) {
|
|
1251
|
+
throw new TypeError("delegation is required");
|
|
1252
|
+
}
|
|
1253
|
+
const normalizedDelegationId =
|
|
1254
|
+
delegationId ? String(delegationId) : delegation.delegationId ? String(delegation.delegationId) : null;
|
|
1255
|
+
if (!normalizedDelegationId) throw new TypeError("delegationId is required");
|
|
1256
|
+
|
|
1257
|
+
const updatedAt = parseIsoOrNull(delegation.updatedAt) ?? new Date().toISOString();
|
|
1258
|
+
const normalizedDelegation = {
|
|
1259
|
+
...delegation,
|
|
1260
|
+
tenantId,
|
|
1261
|
+
delegationId: normalizedDelegationId,
|
|
1262
|
+
updatedAt
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
await client.query(
|
|
1266
|
+
`
|
|
1267
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1268
|
+
VALUES ($1, 'agreement_delegation', $2, 0, NULL, $3, $4)
|
|
1269
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
|
|
1270
|
+
snapshot_json = EXCLUDED.snapshot_json,
|
|
1271
|
+
updated_at = EXCLUDED.updated_at
|
|
1272
|
+
`,
|
|
1273
|
+
[tenantId, normalizedDelegationId, JSON.stringify(normalizedDelegation), updatedAt]
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
async function persistX402Gate(client, { tenantId, gateId, gate }) {
|
|
1278
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1279
|
+
if (!gate || typeof gate !== "object" || Array.isArray(gate)) {
|
|
1280
|
+
throw new TypeError("gate is required");
|
|
1281
|
+
}
|
|
1282
|
+
const normalizedGateId = gateId ? String(gateId) : gate.gateId ? String(gate.gateId) : gate.id ? String(gate.id) : null;
|
|
1283
|
+
if (!normalizedGateId) throw new TypeError("gateId is required");
|
|
1284
|
+
|
|
1285
|
+
const updatedAt = parseIsoOrNull(gate.updatedAt) ?? new Date().toISOString();
|
|
1286
|
+
const normalizedGate = {
|
|
1287
|
+
...gate,
|
|
1288
|
+
tenantId,
|
|
1289
|
+
gateId: normalizedGateId,
|
|
1290
|
+
updatedAt
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
await client.query(
|
|
1294
|
+
`
|
|
1295
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1296
|
+
VALUES ($1, 'x402_gate', $2, 0, NULL, $3, $4)
|
|
1297
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
|
|
1298
|
+
snapshot_json = EXCLUDED.snapshot_json,
|
|
1299
|
+
updated_at = EXCLUDED.updated_at
|
|
1300
|
+
`,
|
|
1301
|
+
[tenantId, normalizedGateId, JSON.stringify(normalizedGate), updatedAt]
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
async function persistX402AgentLifecycle(client, { tenantId, agentId, agentLifecycle }) {
|
|
1306
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1307
|
+
if (!agentLifecycle || typeof agentLifecycle !== "object" || Array.isArray(agentLifecycle)) {
|
|
1308
|
+
throw new TypeError("agentLifecycle is required");
|
|
1309
|
+
}
|
|
1310
|
+
const normalizedAgentId =
|
|
1311
|
+
agentId ? String(agentId) : agentLifecycle.agentId ? String(agentLifecycle.agentId) : null;
|
|
1312
|
+
if (!normalizedAgentId) throw new TypeError("agentId is required");
|
|
1313
|
+
const status = String(agentLifecycle.status ?? "").trim().toLowerCase();
|
|
1314
|
+
if (status !== "active" && status !== "frozen" && status !== "archived") {
|
|
1315
|
+
throw new TypeError("agentLifecycle.status must be active|frozen|archived");
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
const updatedAt = parseIsoOrNull(agentLifecycle.updatedAt) ?? new Date().toISOString();
|
|
1319
|
+
const normalizedAgentLifecycle = {
|
|
1320
|
+
...agentLifecycle,
|
|
1321
|
+
tenantId,
|
|
1322
|
+
agentId: normalizedAgentId,
|
|
1323
|
+
status,
|
|
1324
|
+
updatedAt
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
await client.query(
|
|
1328
|
+
`
|
|
1329
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1330
|
+
VALUES ($1, 'x402_agent_lifecycle', $2, 0, NULL, $3, $4)
|
|
1331
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
|
|
1332
|
+
snapshot_json = EXCLUDED.snapshot_json,
|
|
1333
|
+
updated_at = EXCLUDED.updated_at
|
|
1334
|
+
`,
|
|
1335
|
+
[tenantId, normalizedAgentId, JSON.stringify(normalizedAgentLifecycle), updatedAt]
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
async function persistX402Receipt(client, { tenantId, receiptId, receipt }) {
|
|
1340
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1341
|
+
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
|
|
1342
|
+
throw new TypeError("receipt is required");
|
|
1343
|
+
}
|
|
1344
|
+
const normalizedReceiptId =
|
|
1345
|
+
receiptId ? String(receiptId) : receipt.receiptId ? String(receipt.receiptId) : null;
|
|
1346
|
+
if (!normalizedReceiptId) throw new TypeError("receiptId is required");
|
|
1347
|
+
|
|
1348
|
+
const updatedAt =
|
|
1349
|
+
parseIsoOrNull(receipt.createdAt) ??
|
|
1350
|
+
parseIsoOrNull(receipt.settledAt) ??
|
|
1351
|
+
parseIsoOrNull(receipt.updatedAt) ??
|
|
1352
|
+
new Date().toISOString();
|
|
1353
|
+
const normalizedReceipt = {
|
|
1354
|
+
...receipt,
|
|
1355
|
+
tenantId,
|
|
1356
|
+
receiptId: normalizedReceiptId,
|
|
1357
|
+
reversal: null,
|
|
1358
|
+
reversalEvents: [],
|
|
1359
|
+
updatedAt
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
const inserted = await client.query(
|
|
1363
|
+
`
|
|
1364
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1365
|
+
VALUES ($1, 'x402_receipt', $2, 0, NULL, $3, $4)
|
|
1366
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO NOTHING
|
|
1367
|
+
RETURNING aggregate_id
|
|
1368
|
+
`,
|
|
1369
|
+
[tenantId, normalizedReceiptId, JSON.stringify(normalizedReceipt), updatedAt]
|
|
1370
|
+
);
|
|
1371
|
+
if (inserted.rows.length > 0) return;
|
|
1372
|
+
|
|
1373
|
+
const existing = await client.query(
|
|
1374
|
+
`
|
|
1375
|
+
SELECT snapshot_json
|
|
1376
|
+
FROM snapshots
|
|
1377
|
+
WHERE tenant_id = $1 AND aggregate_type = 'x402_receipt' AND aggregate_id = $2
|
|
1378
|
+
LIMIT 1
|
|
1379
|
+
`,
|
|
1380
|
+
[tenantId, normalizedReceiptId]
|
|
1381
|
+
);
|
|
1382
|
+
if (!existing.rows.length) return;
|
|
1383
|
+
|
|
1384
|
+
const existingCanonical = canonicalJsonStringify(existing.rows[0]?.snapshot_json ?? null);
|
|
1385
|
+
const incomingCanonical = canonicalJsonStringify(normalizedReceipt);
|
|
1386
|
+
if (existingCanonical !== incomingCanonical) {
|
|
1387
|
+
const err = new Error("x402 receipt is immutable and cannot be changed");
|
|
1388
|
+
err.code = "X402_RECEIPT_IMMUTABLE";
|
|
1389
|
+
err.receiptId = normalizedReceiptId;
|
|
1390
|
+
throw err;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
async function persistX402WalletPolicy(client, { tenantId, policy }) {
|
|
1395
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1396
|
+
if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
|
|
1397
|
+
throw new TypeError("policy is required");
|
|
1398
|
+
}
|
|
1399
|
+
const sponsorWalletRef =
|
|
1400
|
+
policy.sponsorWalletRef && String(policy.sponsorWalletRef).trim() !== ""
|
|
1401
|
+
? String(policy.sponsorWalletRef).trim()
|
|
1402
|
+
: null;
|
|
1403
|
+
const policyRef = policy.policyRef && String(policy.policyRef).trim() !== "" ? String(policy.policyRef).trim() : null;
|
|
1404
|
+
const policyVersion = parseSafeIntegerOrNull(policy.policyVersion);
|
|
1405
|
+
if (!sponsorWalletRef) throw new TypeError("policy.sponsorWalletRef is required");
|
|
1406
|
+
if (!policyRef) throw new TypeError("policy.policyRef is required");
|
|
1407
|
+
if (policyVersion === null || policyVersion <= 0) throw new TypeError("policy.policyVersion must be >= 1");
|
|
1408
|
+
const aggregateId = `${sponsorWalletRef}::${policyRef}::${policyVersion}`;
|
|
1409
|
+
const updatedAt = parseIsoOrNull(policy.updatedAt) ?? new Date().toISOString();
|
|
1410
|
+
const normalizedPolicy = {
|
|
1411
|
+
...policy,
|
|
1412
|
+
tenantId,
|
|
1413
|
+
sponsorWalletRef,
|
|
1414
|
+
policyRef,
|
|
1415
|
+
policyVersion,
|
|
1416
|
+
updatedAt
|
|
1417
|
+
};
|
|
1418
|
+
|
|
1419
|
+
await client.query(
|
|
1420
|
+
`
|
|
1421
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1422
|
+
VALUES ($1, 'x402_wallet_policy', $2, 0, NULL, $3, $4)
|
|
1423
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
|
|
1424
|
+
snapshot_json = EXCLUDED.snapshot_json,
|
|
1425
|
+
updated_at = EXCLUDED.updated_at
|
|
1426
|
+
`,
|
|
1427
|
+
[tenantId, aggregateId, JSON.stringify(normalizedPolicy), updatedAt]
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
async function persistX402ZkVerificationKey(client, { tenantId, verificationKeyId, verificationKey }) {
|
|
1432
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1433
|
+
if (!verificationKey || typeof verificationKey !== "object" || Array.isArray(verificationKey)) {
|
|
1434
|
+
throw new TypeError("verificationKey is required");
|
|
1435
|
+
}
|
|
1436
|
+
const normalizedVerificationKeyId =
|
|
1437
|
+
verificationKeyId && String(verificationKeyId).trim() !== ""
|
|
1438
|
+
? String(verificationKeyId).trim()
|
|
1439
|
+
: verificationKey.verificationKeyId && String(verificationKey.verificationKeyId).trim() !== ""
|
|
1440
|
+
? String(verificationKey.verificationKeyId).trim()
|
|
1441
|
+
: null;
|
|
1442
|
+
if (!normalizedVerificationKeyId) throw new TypeError("verificationKeyId is required");
|
|
1443
|
+
const createdAt =
|
|
1444
|
+
parseIsoOrNull(verificationKey.createdAt) ??
|
|
1445
|
+
parseIsoOrNull(verificationKey.updatedAt) ??
|
|
1446
|
+
new Date().toISOString();
|
|
1447
|
+
const normalizedVerificationKey = {
|
|
1448
|
+
...verificationKey,
|
|
1449
|
+
tenantId,
|
|
1450
|
+
verificationKeyId: normalizedVerificationKeyId,
|
|
1451
|
+
createdAt,
|
|
1452
|
+
updatedAt: createdAt
|
|
1453
|
+
};
|
|
1454
|
+
|
|
1455
|
+
const inserted = await client.query(
|
|
1456
|
+
`
|
|
1457
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1458
|
+
VALUES ($1, 'x402_zk_verification_key', $2, 0, NULL, $3, $4)
|
|
1459
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO NOTHING
|
|
1460
|
+
RETURNING aggregate_id
|
|
1461
|
+
`,
|
|
1462
|
+
[tenantId, normalizedVerificationKeyId, JSON.stringify(normalizedVerificationKey), createdAt]
|
|
1463
|
+
);
|
|
1464
|
+
if (inserted.rows.length > 0) return;
|
|
1465
|
+
|
|
1466
|
+
const existing = await client.query(
|
|
1467
|
+
`
|
|
1468
|
+
SELECT snapshot_json
|
|
1469
|
+
FROM snapshots
|
|
1470
|
+
WHERE tenant_id = $1 AND aggregate_type = 'x402_zk_verification_key' AND aggregate_id = $2
|
|
1471
|
+
LIMIT 1
|
|
1472
|
+
`,
|
|
1473
|
+
[tenantId, normalizedVerificationKeyId]
|
|
1474
|
+
);
|
|
1475
|
+
if (!existing.rows.length) return;
|
|
1476
|
+
|
|
1477
|
+
const existingCanonical = canonicalJsonStringify(existing.rows[0]?.snapshot_json ?? null);
|
|
1478
|
+
const incomingCanonical = canonicalJsonStringify(normalizedVerificationKey);
|
|
1479
|
+
if (existingCanonical !== incomingCanonical) {
|
|
1480
|
+
const err = new Error("x402 zk verification key is immutable and cannot be changed");
|
|
1481
|
+
err.code = "X402_ZK_VERIFICATION_KEY_IMMUTABLE";
|
|
1482
|
+
err.verificationKeyId = normalizedVerificationKeyId;
|
|
1483
|
+
throw err;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
async function persistX402ReversalEvent(client, { tenantId, gateId, eventId, event }) {
|
|
1488
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1489
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) {
|
|
1490
|
+
throw new TypeError("event is required");
|
|
1491
|
+
}
|
|
1492
|
+
const normalizedEventId = eventId ? String(eventId) : event.eventId ? String(event.eventId) : event.id ? String(event.id) : null;
|
|
1493
|
+
if (!normalizedEventId) throw new TypeError("eventId is required");
|
|
1494
|
+
const normalizedGateId = gateId ? String(gateId) : event.gateId ? String(event.gateId) : null;
|
|
1495
|
+
if (!normalizedGateId) throw new TypeError("gateId is required");
|
|
1496
|
+
const updatedAt = parseIsoOrNull(event.occurredAt ?? event.createdAt) ?? new Date().toISOString();
|
|
1497
|
+
const normalizedEvent = {
|
|
1498
|
+
...event,
|
|
1499
|
+
tenantId,
|
|
1500
|
+
gateId: normalizedGateId,
|
|
1501
|
+
eventId: normalizedEventId,
|
|
1502
|
+
occurredAt: parseIsoOrNull(event.occurredAt) ?? updatedAt
|
|
1503
|
+
};
|
|
1504
|
+
await client.query(
|
|
1505
|
+
`
|
|
1506
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1507
|
+
VALUES ($1, 'x402_reversal_event', $2, 0, NULL, $3, $4)
|
|
1508
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
|
|
1509
|
+
snapshot_json = EXCLUDED.snapshot_json,
|
|
1510
|
+
updated_at = EXCLUDED.updated_at
|
|
1511
|
+
`,
|
|
1512
|
+
[tenantId, normalizedEventId, JSON.stringify(normalizedEvent), updatedAt]
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
async function persistX402ReversalNonceUsage(client, { tenantId, sponsorRef, nonce, usage }) {
|
|
1517
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1518
|
+
if (!usage || typeof usage !== "object" || Array.isArray(usage)) throw new TypeError("usage is required");
|
|
1519
|
+
const normalizedSponsorRef =
|
|
1520
|
+
sponsorRef && String(sponsorRef).trim() !== ""
|
|
1521
|
+
? String(sponsorRef).trim()
|
|
1522
|
+
: usage.sponsorRef && String(usage.sponsorRef).trim() !== ""
|
|
1523
|
+
? String(usage.sponsorRef).trim()
|
|
1524
|
+
: null;
|
|
1525
|
+
const normalizedNonce =
|
|
1526
|
+
nonce && String(nonce).trim() !== ""
|
|
1527
|
+
? String(nonce).trim()
|
|
1528
|
+
: usage.nonce && String(usage.nonce).trim() !== ""
|
|
1529
|
+
? String(usage.nonce).trim()
|
|
1530
|
+
: null;
|
|
1531
|
+
if (!normalizedSponsorRef) throw new TypeError("sponsorRef is required");
|
|
1532
|
+
if (!normalizedNonce) throw new TypeError("nonce is required");
|
|
1533
|
+
const aggregateId = `${normalizedSponsorRef}::${normalizedNonce}`;
|
|
1534
|
+
const updatedAt = parseIsoOrNull(usage.usedAt) ?? new Date().toISOString();
|
|
1535
|
+
const normalizedUsage = {
|
|
1536
|
+
...usage,
|
|
1537
|
+
tenantId,
|
|
1538
|
+
sponsorRef: normalizedSponsorRef,
|
|
1539
|
+
nonce: normalizedNonce,
|
|
1540
|
+
usedAt: parseIsoOrNull(usage.usedAt) ?? updatedAt
|
|
1541
|
+
};
|
|
1542
|
+
await client.query(
|
|
1543
|
+
`
|
|
1544
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1545
|
+
VALUES ($1, 'x402_reversal_nonce', $2, 0, NULL, $3, $4)
|
|
1546
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
|
|
1547
|
+
snapshot_json = EXCLUDED.snapshot_json,
|
|
1548
|
+
updated_at = EXCLUDED.updated_at
|
|
1549
|
+
`,
|
|
1550
|
+
[tenantId, aggregateId, JSON.stringify(normalizedUsage), updatedAt]
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
async function persistX402ReversalCommandUsage(client, { tenantId, commandId, usage }) {
|
|
1555
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1556
|
+
if (!usage || typeof usage !== "object" || Array.isArray(usage)) throw new TypeError("usage is required");
|
|
1557
|
+
const normalizedCommandId =
|
|
1558
|
+
commandId && String(commandId).trim() !== ""
|
|
1559
|
+
? String(commandId).trim()
|
|
1560
|
+
: usage.commandId && String(usage.commandId).trim() !== ""
|
|
1561
|
+
? String(usage.commandId).trim()
|
|
1562
|
+
: null;
|
|
1563
|
+
if (!normalizedCommandId) throw new TypeError("commandId is required");
|
|
1564
|
+
const updatedAt = parseIsoOrNull(usage.usedAt) ?? new Date().toISOString();
|
|
1565
|
+
const normalizedUsage = {
|
|
1566
|
+
...usage,
|
|
1567
|
+
tenantId,
|
|
1568
|
+
commandId: normalizedCommandId,
|
|
1569
|
+
usedAt: parseIsoOrNull(usage.usedAt) ?? updatedAt
|
|
1570
|
+
};
|
|
1571
|
+
await client.query(
|
|
1572
|
+
`
|
|
1573
|
+
INSERT INTO snapshots (tenant_id, aggregate_type, aggregate_id, seq, at_chain_hash, snapshot_json, updated_at)
|
|
1574
|
+
VALUES ($1, 'x402_reversal_command', $2, 0, NULL, $3, $4)
|
|
1575
|
+
ON CONFLICT (tenant_id, aggregate_type, aggregate_id) DO UPDATE SET
|
|
1576
|
+
snapshot_json = EXCLUDED.snapshot_json,
|
|
1577
|
+
updated_at = EXCLUDED.updated_at
|
|
1578
|
+
`,
|
|
1579
|
+
[tenantId, normalizedCommandId, JSON.stringify(normalizedUsage), updatedAt]
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1152
1583
|
async function persistToolCallHold(client, { tenantId, holdHash, hold }) {
|
|
1153
1584
|
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
1154
1585
|
if (!hold || typeof hold !== "object" || Array.isArray(hold)) {
|
|
@@ -1643,6 +2074,15 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
1643
2074
|
"AGENT_RUN_EVENTS_APPENDED",
|
|
1644
2075
|
"AGENT_RUN_SETTLEMENT_UPSERT",
|
|
1645
2076
|
"ARBITRATION_CASE_UPSERT",
|
|
2077
|
+
"AGREEMENT_DELEGATION_UPSERT",
|
|
2078
|
+
"X402_GATE_UPSERT",
|
|
2079
|
+
"X402_AGENT_LIFECYCLE_UPSERT",
|
|
2080
|
+
"X402_RECEIPT_PUT",
|
|
2081
|
+
"X402_WALLET_POLICY_UPSERT",
|
|
2082
|
+
"X402_ZK_VERIFICATION_KEY_PUT",
|
|
2083
|
+
"X402_REVERSAL_EVENT_APPEND",
|
|
2084
|
+
"X402_REVERSAL_NONCE_PUT",
|
|
2085
|
+
"X402_REVERSAL_COMMAND_PUT",
|
|
1646
2086
|
"TOOL_CALL_HOLD_UPSERT",
|
|
1647
2087
|
"SETTLEMENT_ADJUSTMENT_PUT",
|
|
1648
2088
|
"MARKETPLACE_RFQ_UPSERT",
|
|
@@ -2028,6 +2468,45 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
2028
2468
|
);
|
|
2029
2469
|
}
|
|
2030
2470
|
|
|
2471
|
+
async function persistReputationEventIndexRow(client, { tenantId, artifactId, artifactHash, artifact }) {
|
|
2472
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
2473
|
+
if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) return;
|
|
2474
|
+
if (String(artifact.artifactType ?? artifact.schemaVersion ?? "") !== "ReputationEvent.v1") return;
|
|
2475
|
+
|
|
2476
|
+
const subject = artifact.subject && typeof artifact.subject === "object" && !Array.isArray(artifact.subject) ? artifact.subject : null;
|
|
2477
|
+
const sourceRef = artifact.sourceRef && typeof artifact.sourceRef === "object" && !Array.isArray(artifact.sourceRef) ? artifact.sourceRef : null;
|
|
2478
|
+
const agentId = subject?.agentId ? String(subject.agentId).trim() : "";
|
|
2479
|
+
if (!agentId) return;
|
|
2480
|
+
|
|
2481
|
+
const toolIdRaw = subject?.toolId ? String(subject.toolId).trim() : "";
|
|
2482
|
+
const toolId = toolIdRaw === "" ? null : toolIdRaw;
|
|
2483
|
+
const sourceKindRaw = sourceRef?.kind ? String(sourceRef.kind).trim().toLowerCase() : "";
|
|
2484
|
+
const sourceKind = sourceKindRaw === "" ? "unknown" : sourceKindRaw;
|
|
2485
|
+
const sourceHashRaw = sourceRef?.hash ? String(sourceRef.hash).trim().toLowerCase() : "";
|
|
2486
|
+
const sourceHash = sourceHashRaw === "" ? null : sourceHashRaw;
|
|
2487
|
+
const eventKindRaw = artifact?.eventKind ? String(artifact.eventKind).trim().toLowerCase() : "";
|
|
2488
|
+
const eventKind = eventKindRaw === "" ? "unknown" : eventKindRaw;
|
|
2489
|
+
const occurredAtParsed = Date.parse(String(artifact.occurredAt ?? ""));
|
|
2490
|
+
const occurredAt = Number.isFinite(occurredAtParsed) ? new Date(occurredAtParsed).toISOString() : new Date().toISOString();
|
|
2491
|
+
|
|
2492
|
+
await client.query(
|
|
2493
|
+
`
|
|
2494
|
+
INSERT INTO reputation_event_index (
|
|
2495
|
+
tenant_id, artifact_id, artifact_hash, subject_agent_id, subject_tool_id, occurred_at, event_kind, source_kind, source_hash
|
|
2496
|
+
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
|
|
2497
|
+
ON CONFLICT (tenant_id, artifact_id) DO UPDATE SET
|
|
2498
|
+
artifact_hash = EXCLUDED.artifact_hash,
|
|
2499
|
+
subject_agent_id = EXCLUDED.subject_agent_id,
|
|
2500
|
+
subject_tool_id = EXCLUDED.subject_tool_id,
|
|
2501
|
+
occurred_at = EXCLUDED.occurred_at,
|
|
2502
|
+
event_kind = EXCLUDED.event_kind,
|
|
2503
|
+
source_kind = EXCLUDED.source_kind,
|
|
2504
|
+
source_hash = EXCLUDED.source_hash
|
|
2505
|
+
`,
|
|
2506
|
+
[tenantId, String(artifactId), String(artifactHash), agentId, toolId, occurredAt, eventKind, sourceKind, sourceHash]
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2031
2510
|
async function persistArtifactRow(client, { tenantId, artifact }) {
|
|
2032
2511
|
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
2033
2512
|
if (!artifact || typeof artifact !== "object") throw new TypeError("artifact is required");
|
|
@@ -2040,9 +2519,12 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
2040
2519
|
const jobId = String(artifact.jobId ?? "");
|
|
2041
2520
|
const sourceEventId = typeof artifact.sourceEventId === "string" && artifact.sourceEventId.trim() !== "" ? String(artifact.sourceEventId) : "";
|
|
2042
2521
|
|
|
2043
|
-
// Invariant: for artifacts tied to a specific source event, there must be exactly one artifact per
|
|
2522
|
+
// Invariant: for artifacts tied to a specific *job* source event, there must be exactly one artifact per
|
|
2044
2523
|
// (jobId + artifactType + sourceEventId). This prevents duplicate settlement-backed certificates.
|
|
2045
|
-
|
|
2524
|
+
//
|
|
2525
|
+
// Important: many non-job artifacts (month close statements, party statements, payout instructions, etc.) set a
|
|
2526
|
+
// sourceEventId but intentionally do not have a jobId. Do not apply this invariant to those artifacts.
|
|
2527
|
+
if (sourceEventId && jobId) {
|
|
2046
2528
|
const existingBySource = await client.query(
|
|
2047
2529
|
"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
2530
|
[tenantId, jobId, artifactType, sourceEventId]
|
|
@@ -2058,80 +2540,89 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
2058
2540
|
err.gotArtifactHash = String(artifactHash);
|
|
2059
2541
|
throw err;
|
|
2060
2542
|
}
|
|
2543
|
+
await persistReputationEventIndexRow(client, { tenantId, artifactId: currentId, artifactHash: currentHash, artifact });
|
|
2061
2544
|
return;
|
|
2062
2545
|
}
|
|
2063
2546
|
}
|
|
2064
2547
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2548
|
+
const existing = await client.query("SELECT artifact_hash FROM artifacts WHERE tenant_id = $1 AND artifact_id = $2", [tenantId, artifactId]);
|
|
2549
|
+
if (existing.rows.length) {
|
|
2550
|
+
const current = String(existing.rows[0].artifact_hash);
|
|
2551
|
+
if (current !== String(artifactHash)) {
|
|
2552
|
+
const err = new Error("artifactId already exists with a different hash");
|
|
2553
|
+
err.code = "ARTIFACT_HASH_MISMATCH";
|
|
2554
|
+
err.expectedArtifactHash = current;
|
|
2555
|
+
err.gotArtifactHash = String(artifactHash);
|
|
2556
|
+
throw err;
|
|
2557
|
+
}
|
|
2558
|
+
await persistReputationEventIndexRow(client, { tenantId, artifactId, artifactHash: current, artifact });
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// Avoid transaction-aborting unique-constraint errors (we run inside explicit transactions).
|
|
2563
|
+
// Handle all unique conflicts by doing nothing and then checking what already exists.
|
|
2564
|
+
const insertRes = await client.query(
|
|
2565
|
+
`
|
|
2566
|
+
INSERT INTO artifacts (tenant_id, artifact_id, artifact_type, job_id, at_chain_hash, source_event_id, artifact_hash, artifact_json)
|
|
2567
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
|
|
2568
|
+
ON CONFLICT DO NOTHING
|
|
2569
|
+
`,
|
|
2570
|
+
[
|
|
2571
|
+
tenantId,
|
|
2572
|
+
String(artifactId),
|
|
2573
|
+
artifactType,
|
|
2574
|
+
jobId,
|
|
2575
|
+
String(artifact.atChainHash ?? artifact.eventProof?.lastChainHash ?? ""),
|
|
2576
|
+
sourceEventId,
|
|
2577
|
+
String(artifactHash),
|
|
2578
|
+
JSON.stringify(artifact)
|
|
2579
|
+
]
|
|
2580
|
+
);
|
|
2581
|
+
if (Number(insertRes?.rowCount ?? 0) > 0) {
|
|
2582
|
+
await persistReputationEventIndexRow(client, { tenantId, artifactId, artifactHash, artifact });
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2077
2585
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
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
|
-
}
|
|
2586
|
+
// Someone else inserted a conflicting row under a unique constraint. Determine if it's idempotent.
|
|
2587
|
+
const byId = await client.query("SELECT artifact_hash FROM artifacts WHERE tenant_id = $1 AND artifact_id = $2", [tenantId, artifactId]);
|
|
2588
|
+
if (byId.rows.length) {
|
|
2589
|
+
const current = String(byId.rows[0].artifact_hash);
|
|
2590
|
+
if (current === String(artifactHash)) {
|
|
2591
|
+
await persistReputationEventIndexRow(client, { tenantId, artifactId, artifactHash: current, artifact });
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
const mismatch = new Error("artifactId already exists with a different hash");
|
|
2595
|
+
mismatch.code = "ARTIFACT_HASH_MISMATCH";
|
|
2596
|
+
mismatch.expectedArtifactHash = current;
|
|
2597
|
+
mismatch.gotArtifactHash = String(artifactHash);
|
|
2598
|
+
throw mismatch;
|
|
2599
|
+
}
|
|
2116
2600
|
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2601
|
+
if (sourceEventId && jobId) {
|
|
2602
|
+
const bySource = await client.query(
|
|
2603
|
+
"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",
|
|
2604
|
+
[tenantId, jobId, artifactType, sourceEventId]
|
|
2605
|
+
);
|
|
2606
|
+
if (bySource.rows.length) {
|
|
2607
|
+
const currentId = String(bySource.rows[0].artifact_id);
|
|
2608
|
+
const currentHash = String(bySource.rows[0].artifact_hash);
|
|
2609
|
+
if (currentHash === String(artifactHash)) {
|
|
2610
|
+
await persistReputationEventIndexRow(client, { tenantId, artifactId: currentId, artifactHash: currentHash, artifact });
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
const conflict = new Error("artifact already exists for this job/type/sourceEventId with a different hash");
|
|
2614
|
+
conflict.code = "ARTIFACT_SOURCE_EVENT_CONFLICT";
|
|
2615
|
+
conflict.existingArtifactId = currentId;
|
|
2616
|
+
conflict.existingArtifactHash = currentHash;
|
|
2617
|
+
conflict.gotArtifactHash = String(artifactHash);
|
|
2618
|
+
throw conflict;
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2127
2621
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
throw err;
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2622
|
+
const raced = new Error("artifact insert raced with another transaction");
|
|
2623
|
+
raced.code = "ARTIFACT_INSERT_RACE";
|
|
2624
|
+
throw raced;
|
|
2625
|
+
}
|
|
2135
2626
|
|
|
2136
2627
|
async function insertDeliveryRow(client, { tenantId, delivery }) {
|
|
2137
2628
|
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
@@ -2415,6 +2906,60 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
2415
2906
|
const tenantId = normalizeTenantId(op.tenantId ?? op.arbitrationCase?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2416
2907
|
await persistArbitrationCase(client, { tenantId, caseId: op.caseId, arbitrationCase: op.arbitrationCase });
|
|
2417
2908
|
}
|
|
2909
|
+
if (op.kind === "AGREEMENT_DELEGATION_UPSERT") {
|
|
2910
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.delegation?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2911
|
+
await persistAgreementDelegation(client, { tenantId, delegationId: op.delegationId, delegation: op.delegation });
|
|
2912
|
+
}
|
|
2913
|
+
if (op.kind === "X402_GATE_UPSERT") {
|
|
2914
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.gate?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2915
|
+
await persistX402Gate(client, { tenantId, gateId: op.gateId, gate: op.gate });
|
|
2916
|
+
}
|
|
2917
|
+
if (op.kind === "X402_AGENT_LIFECYCLE_UPSERT") {
|
|
2918
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.agentLifecycle?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2919
|
+
await persistX402AgentLifecycle(client, { tenantId, agentId: op.agentId, agentLifecycle: op.agentLifecycle });
|
|
2920
|
+
}
|
|
2921
|
+
if (op.kind === "X402_RECEIPT_PUT") {
|
|
2922
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.receipt?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2923
|
+
await persistX402Receipt(client, { tenantId, receiptId: op.receiptId, receipt: op.receipt });
|
|
2924
|
+
}
|
|
2925
|
+
if (op.kind === "X402_WALLET_POLICY_UPSERT") {
|
|
2926
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.policy?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2927
|
+
await persistX402WalletPolicy(client, { tenantId, policy: op.policy });
|
|
2928
|
+
}
|
|
2929
|
+
if (op.kind === "X402_ZK_VERIFICATION_KEY_PUT") {
|
|
2930
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.verificationKey?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2931
|
+
await persistX402ZkVerificationKey(client, {
|
|
2932
|
+
tenantId,
|
|
2933
|
+
verificationKeyId: op.verificationKeyId,
|
|
2934
|
+
verificationKey: op.verificationKey
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
if (op.kind === "X402_REVERSAL_EVENT_APPEND") {
|
|
2938
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.event?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2939
|
+
await persistX402ReversalEvent(client, {
|
|
2940
|
+
tenantId,
|
|
2941
|
+
gateId: op.gateId,
|
|
2942
|
+
eventId: op.eventId,
|
|
2943
|
+
event: op.event
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
if (op.kind === "X402_REVERSAL_NONCE_PUT") {
|
|
2947
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.usage?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2948
|
+
await persistX402ReversalNonceUsage(client, {
|
|
2949
|
+
tenantId,
|
|
2950
|
+
sponsorRef: op.sponsorRef,
|
|
2951
|
+
nonce: op.nonce,
|
|
2952
|
+
usage: op.usage
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
if (op.kind === "X402_REVERSAL_COMMAND_PUT") {
|
|
2956
|
+
const tenantId = normalizeTenantId(op.tenantId ?? op.usage?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2957
|
+
await persistX402ReversalCommandUsage(client, {
|
|
2958
|
+
tenantId,
|
|
2959
|
+
commandId: op.commandId,
|
|
2960
|
+
usage: op.usage
|
|
2961
|
+
});
|
|
2962
|
+
}
|
|
2418
2963
|
if (op.kind === "TOOL_CALL_HOLD_UPSERT") {
|
|
2419
2964
|
const tenantId = normalizeTenantId(op.tenantId ?? op.hold?.tenantId ?? DEFAULT_TENANT_ID);
|
|
2420
2965
|
await persistToolCallHold(client, { tenantId, holdHash: op.holdHash, hold: op.hold });
|
|
@@ -2758,6 +3303,88 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
2758
3303
|
}
|
|
2759
3304
|
};
|
|
2760
3305
|
|
|
3306
|
+
store.sumWalletPolicySpendCentsForDay = async function sumWalletPolicySpendCentsForDay({
|
|
3307
|
+
tenantId = DEFAULT_TENANT_ID,
|
|
3308
|
+
agentId,
|
|
3309
|
+
dayStartIso,
|
|
3310
|
+
dayEndIso
|
|
3311
|
+
} = {}) {
|
|
3312
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
3313
|
+
assertNonEmptyString(agentId, "agentId");
|
|
3314
|
+
assertNonEmptyString(dayStartIso, "dayStartIso");
|
|
3315
|
+
assertNonEmptyString(dayEndIso, "dayEndIso");
|
|
3316
|
+
const startMs = Date.parse(dayStartIso);
|
|
3317
|
+
const endMs = Date.parse(dayEndIso);
|
|
3318
|
+
if (!Number.isFinite(startMs)) throw new TypeError("dayStartIso must be an ISO date string");
|
|
3319
|
+
if (!Number.isFinite(endMs)) throw new TypeError("dayEndIso must be an ISO date string");
|
|
3320
|
+
if (!(endMs > startMs)) throw new TypeError("dayEndIso must be after dayStartIso");
|
|
3321
|
+
|
|
3322
|
+
let runSum = 0;
|
|
3323
|
+
try {
|
|
3324
|
+
const res = await pool.query(
|
|
3325
|
+
`
|
|
3326
|
+
SELECT COALESCE(SUM(amount_cents), 0)::bigint AS c
|
|
3327
|
+
FROM agent_run_settlements
|
|
3328
|
+
WHERE tenant_id = $1
|
|
3329
|
+
AND payer_agent_id = $2
|
|
3330
|
+
AND locked_at >= $3
|
|
3331
|
+
AND locked_at < $4
|
|
3332
|
+
`,
|
|
3333
|
+
[tenantId, String(agentId), String(dayStartIso), String(dayEndIso)]
|
|
3334
|
+
);
|
|
3335
|
+
const n = Number(res.rows[0]?.c ?? 0);
|
|
3336
|
+
runSum = Number.isSafeInteger(n) && n >= 0 ? n : 0;
|
|
3337
|
+
} catch (err) {
|
|
3338
|
+
if (err?.code !== "42P01") throw err;
|
|
3339
|
+
for (const row of store.agentRunSettlements.values()) {
|
|
3340
|
+
if (!row || typeof row !== "object") continue;
|
|
3341
|
+
if (normalizeTenantId(row.tenantId ?? DEFAULT_TENANT_ID) !== tenantId) continue;
|
|
3342
|
+
if (String(row.payerAgentId ?? "") !== String(agentId)) continue;
|
|
3343
|
+
const lockedAt = row.lockedAt ?? null;
|
|
3344
|
+
const lockedMs = typeof lockedAt === "string" ? Date.parse(lockedAt) : NaN;
|
|
3345
|
+
if (!Number.isFinite(lockedMs)) continue;
|
|
3346
|
+
if (lockedMs < startMs || lockedMs >= endMs) continue;
|
|
3347
|
+
const amountCents = Number(row.amountCents ?? 0);
|
|
3348
|
+
if (!Number.isSafeInteger(amountCents) || amountCents <= 0) continue;
|
|
3349
|
+
runSum += amountCents;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
let holdSum = 0;
|
|
3354
|
+
try {
|
|
3355
|
+
const res = await pool.query(
|
|
3356
|
+
`
|
|
3357
|
+
SELECT COALESCE(SUM((snapshot_json->>'heldAmountCents')::bigint), 0)::bigint AS c
|
|
3358
|
+
FROM snapshots
|
|
3359
|
+
WHERE tenant_id = $1
|
|
3360
|
+
AND aggregate_type = 'tool_call_hold'
|
|
3361
|
+
AND snapshot_json->>'payerAgentId' = $2
|
|
3362
|
+
AND (snapshot_json->>'createdAt')::timestamptz >= $3::timestamptz
|
|
3363
|
+
AND (snapshot_json->>'createdAt')::timestamptz < $4::timestamptz
|
|
3364
|
+
`,
|
|
3365
|
+
[tenantId, String(agentId), String(dayStartIso), String(dayEndIso)]
|
|
3366
|
+
);
|
|
3367
|
+
const n = Number(res.rows[0]?.c ?? 0);
|
|
3368
|
+
holdSum = Number.isSafeInteger(n) && n >= 0 ? n : 0;
|
|
3369
|
+
} catch (err) {
|
|
3370
|
+
if (err?.code !== "42P01") throw err;
|
|
3371
|
+
for (const row of store.toolCallHolds.values()) {
|
|
3372
|
+
if (!row || typeof row !== "object") continue;
|
|
3373
|
+
if (normalizeTenantId(row.tenantId ?? DEFAULT_TENANT_ID) !== tenantId) continue;
|
|
3374
|
+
if (String(row.payerAgentId ?? "") !== String(agentId)) continue;
|
|
3375
|
+
const createdAt = row.createdAt ?? null;
|
|
3376
|
+
const createdMs = typeof createdAt === "string" ? Date.parse(createdAt) : NaN;
|
|
3377
|
+
if (!Number.isFinite(createdMs)) continue;
|
|
3378
|
+
if (createdMs < startMs || createdMs >= endMs) continue;
|
|
3379
|
+
const heldAmountCents = Number(row.heldAmountCents ?? 0);
|
|
3380
|
+
if (!Number.isSafeInteger(heldAmountCents) || heldAmountCents <= 0) continue;
|
|
3381
|
+
holdSum += heldAmountCents;
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
return runSum + holdSum;
|
|
3386
|
+
};
|
|
3387
|
+
|
|
2761
3388
|
function arbitrationCaseSnapshotRowToRecord(row) {
|
|
2762
3389
|
const arbitrationCase = row?.snapshot_json ?? null;
|
|
2763
3390
|
if (!arbitrationCase || typeof arbitrationCase !== "object" || Array.isArray(arbitrationCase)) return null;
|
|
@@ -4734,6 +5361,58 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
4734
5361
|
return res.rows.map((r) => r.artifact_json);
|
|
4735
5362
|
};
|
|
4736
5363
|
|
|
5364
|
+
store.listReputationEvents = async function listReputationEvents({
|
|
5365
|
+
tenantId = DEFAULT_TENANT_ID,
|
|
5366
|
+
agentId,
|
|
5367
|
+
toolId = null,
|
|
5368
|
+
occurredAtGte = null,
|
|
5369
|
+
occurredAtLte = null,
|
|
5370
|
+
limit = 1000,
|
|
5371
|
+
offset = 0
|
|
5372
|
+
} = {}) {
|
|
5373
|
+
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
5374
|
+
assertNonEmptyString(agentId, "agentId");
|
|
5375
|
+
if (toolId !== null && toolId !== undefined) assertNonEmptyString(toolId, "toolId");
|
|
5376
|
+
if (occurredAtGte !== null && occurredAtGte !== undefined) assertNonEmptyString(occurredAtGte, "occurredAtGte");
|
|
5377
|
+
if (occurredAtLte !== null && occurredAtLte !== undefined) assertNonEmptyString(occurredAtLte, "occurredAtLte");
|
|
5378
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) throw new TypeError("limit must be a positive safe integer");
|
|
5379
|
+
if (!Number.isSafeInteger(offset) || offset < 0) throw new TypeError("offset must be a non-negative safe integer");
|
|
5380
|
+
|
|
5381
|
+
const params = [tenantId, String(agentId)];
|
|
5382
|
+
const where = ["idx.tenant_id = $1", "idx.subject_agent_id = $2"];
|
|
5383
|
+
if (toolId !== null && toolId !== undefined) {
|
|
5384
|
+
params.push(String(toolId));
|
|
5385
|
+
where.push(`idx.subject_tool_id = $${params.length}`);
|
|
5386
|
+
}
|
|
5387
|
+
if (occurredAtGte !== null && occurredAtGte !== undefined) {
|
|
5388
|
+
params.push(String(occurredAtGte));
|
|
5389
|
+
where.push(`idx.occurred_at >= $${params.length}::timestamptz`);
|
|
5390
|
+
}
|
|
5391
|
+
if (occurredAtLte !== null && occurredAtLte !== undefined) {
|
|
5392
|
+
params.push(String(occurredAtLte));
|
|
5393
|
+
where.push(`idx.occurred_at <= $${params.length}::timestamptz`);
|
|
5394
|
+
}
|
|
5395
|
+
const safeLimit = Math.min(5000, limit);
|
|
5396
|
+
params.push(safeLimit);
|
|
5397
|
+
params.push(offset);
|
|
5398
|
+
|
|
5399
|
+
const res = await pool.query(
|
|
5400
|
+
`
|
|
5401
|
+
SELECT a.artifact_json
|
|
5402
|
+
FROM reputation_event_index idx
|
|
5403
|
+
INNER JOIN artifacts a
|
|
5404
|
+
ON a.tenant_id = idx.tenant_id
|
|
5405
|
+
AND a.artifact_id = idx.artifact_id
|
|
5406
|
+
WHERE ${where.join(" AND ")}
|
|
5407
|
+
ORDER BY idx.occurred_at ASC, idx.artifact_id ASC
|
|
5408
|
+
LIMIT $${params.length - 1}
|
|
5409
|
+
OFFSET $${params.length}
|
|
5410
|
+
`,
|
|
5411
|
+
params
|
|
5412
|
+
);
|
|
5413
|
+
return res.rows.map((row) => row.artifact_json);
|
|
5414
|
+
};
|
|
5415
|
+
|
|
4737
5416
|
store.createDelivery = async function createDelivery({ tenantId = DEFAULT_TENANT_ID, delivery }) {
|
|
4738
5417
|
tenantId = normalizeTenantId(tenantId ?? DEFAULT_TENANT_ID);
|
|
4739
5418
|
if (!delivery || typeof delivery !== "object") throw new TypeError("delivery is required");
|
|
@@ -5596,7 +6275,10 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
5596
6275
|
`,
|
|
5597
6276
|
[tenantId, periodStart, periodEnd]
|
|
5598
6277
|
);
|
|
5599
|
-
const jobIds = settledIdsRes.rows
|
|
6278
|
+
const jobIds = settledIdsRes.rows
|
|
6279
|
+
.map((r) => String(r.aggregate_id))
|
|
6280
|
+
.filter((id) => id && id.trim() !== "")
|
|
6281
|
+
.sort((a, b) => a.localeCompare(b));
|
|
5600
6282
|
|
|
5601
6283
|
const jobs = [];
|
|
5602
6284
|
if (jobIds.length) {
|
|
@@ -5604,7 +6286,11 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
5604
6286
|
"SELECT aggregate_id, snapshot_json FROM snapshots WHERE tenant_id = $1 AND aggregate_type = 'job' AND aggregate_id = ANY($2::text[])",
|
|
5605
6287
|
[tenantId, jobIds]
|
|
5606
6288
|
);
|
|
5607
|
-
for (
|
|
6289
|
+
// Determinism: DB may return rows in arbitrary order for ANY($2), but artifacts must be hash-stable.
|
|
6290
|
+
const ordered = snapRes.rows
|
|
6291
|
+
.slice()
|
|
6292
|
+
.sort((a, b) => String(a.aggregate_id ?? "").localeCompare(String(b.aggregate_id ?? "")));
|
|
6293
|
+
for (const r of ordered) {
|
|
5608
6294
|
jobs.push(r.snapshot_json);
|
|
5609
6295
|
}
|
|
5610
6296
|
}
|
|
@@ -6395,13 +7081,93 @@ export async function createPgStore({ databaseUrl, schema = "public", dropSchema
|
|
|
6395
7081
|
return { processed, worker };
|
|
6396
7082
|
}
|
|
6397
7083
|
|
|
7084
|
+
async function processNoopOutboxTopic({ topic, worker, maxMessages = Number.MAX_SAFE_INTEGER } = {}) {
|
|
7085
|
+
if (typeof topic !== "string" || topic.trim() === "") throw new TypeError("topic is required");
|
|
7086
|
+
if (typeof worker !== "string" || worker.trim() === "") throw new TypeError("worker is required");
|
|
7087
|
+
if (!Number.isSafeInteger(maxMessages) || maxMessages < 0) throw new TypeError("maxMessages must be a non-negative safe integer");
|
|
7088
|
+
|
|
7089
|
+
const processed = [];
|
|
7090
|
+
|
|
7091
|
+
// These topics are informational today (no worker consumes them in pg mode).
|
|
7092
|
+
// Drain them deterministically so ops / hosted-baseline checks don't wedge.
|
|
7093
|
+
while (processed.length < maxMessages) {
|
|
7094
|
+
const claimed = await store.claimOutbox({ topic, maxMessages: Math.min(1000, maxMessages - processed.length), worker });
|
|
7095
|
+
if (!claimed.length) break;
|
|
7096
|
+
const ids = claimed.map((r) => r.id);
|
|
7097
|
+
await store.markOutboxProcessed({ ids, lastError: "ok:noop" });
|
|
7098
|
+
for (const id of ids) processed.push({ id, status: "noop" });
|
|
7099
|
+
}
|
|
7100
|
+
|
|
7101
|
+
return { processed, worker };
|
|
7102
|
+
}
|
|
7103
|
+
|
|
6398
7104
|
store.processOutbox = async function processOutbox({ maxMessages = 1000 } = {}) {
|
|
6399
7105
|
const ledger = await processLedgerOutbox({ maxMessages });
|
|
6400
7106
|
const notifications = await processNotificationsOutbox({ maxMessages });
|
|
6401
7107
|
const correlations = await processCorrelationsOutbox({ maxMessages });
|
|
7108
|
+
const jobStatusChanged = await processNoopOutboxTopic({ topic: "JOB_STATUS_CHANGED", worker: "job_status_changed_v0", maxMessages });
|
|
7109
|
+
const jobSettled = await processNoopOutboxTopic({ topic: "JOB_SETTLED", worker: "job_settled_v0", maxMessages });
|
|
6402
7110
|
const monthClose = await processMonthCloseOutbox({ maxMessages });
|
|
6403
7111
|
const financePack = await processFinancePackOutbox({ maxMessages });
|
|
6404
|
-
return { ledger, notifications, correlations, monthClose, financePack };
|
|
7112
|
+
return { ledger, notifications, correlations, jobStatusChanged, jobSettled, monthClose, financePack };
|
|
7113
|
+
};
|
|
7114
|
+
|
|
7115
|
+
// Read-only ops debugging helper (used by /ops/debug/outbox).
|
|
7116
|
+
// Intentionally narrow: surfaces enough to diagnose stuck/DLQ outbox without direct DB access.
|
|
7117
|
+
store.listOutboxDebug = async function listOutboxDebug({
|
|
7118
|
+
topic = null,
|
|
7119
|
+
tenantId = null,
|
|
7120
|
+
includeProcessed = false,
|
|
7121
|
+
state = null,
|
|
7122
|
+
limit = 50
|
|
7123
|
+
} = {}) {
|
|
7124
|
+
const safeLimit = Number.isSafeInteger(Number(limit)) ? Number(limit) : 50;
|
|
7125
|
+
if (safeLimit <= 0 || safeLimit > 500) throw new TypeError("limit must be a safe integer between 1 and 500");
|
|
7126
|
+
const t = typeof topic === "string" && topic.trim() ? topic.trim() : null;
|
|
7127
|
+
const tenant = typeof tenantId === "string" && tenantId.trim() ? normalizeTenantId(tenantId) : null;
|
|
7128
|
+
const normalizedState = typeof state === "string" && state.trim() ? state.trim().toLowerCase() : null;
|
|
7129
|
+
if (
|
|
7130
|
+
normalizedState !== null &&
|
|
7131
|
+
normalizedState !== "pending" &&
|
|
7132
|
+
normalizedState !== "processed" &&
|
|
7133
|
+
normalizedState !== "dlq" &&
|
|
7134
|
+
normalizedState !== "all"
|
|
7135
|
+
) {
|
|
7136
|
+
throw new TypeError("state must be one of pending|processed|dlq|all");
|
|
7137
|
+
}
|
|
7138
|
+
|
|
7139
|
+
return await withTx({ statementTimeoutMs: workerStatementTimeoutMs }, async (client) => {
|
|
7140
|
+
const params = [];
|
|
7141
|
+
let where = `WHERE 1=1`;
|
|
7142
|
+
if (normalizedState === "pending") {
|
|
7143
|
+
where += ` AND processed_at IS NULL`;
|
|
7144
|
+
} else if (normalizedState === "processed") {
|
|
7145
|
+
where += ` AND processed_at IS NOT NULL AND (last_error IS NULL OR last_error NOT LIKE 'DLQ:%')`;
|
|
7146
|
+
} else if (normalizedState === "dlq") {
|
|
7147
|
+
where += ` AND processed_at IS NOT NULL AND last_error LIKE 'DLQ:%'`;
|
|
7148
|
+
} else if (normalizedState !== "all" && !includeProcessed) {
|
|
7149
|
+
// Backward compatibility when state is omitted.
|
|
7150
|
+
where += ` AND processed_at IS NULL`;
|
|
7151
|
+
}
|
|
7152
|
+
if (t) {
|
|
7153
|
+
params.push(String(t));
|
|
7154
|
+
where += ` AND topic = $${params.length}`;
|
|
7155
|
+
}
|
|
7156
|
+
if (tenant) {
|
|
7157
|
+
params.push(String(tenant));
|
|
7158
|
+
where += ` AND tenant_id = $${params.length}`;
|
|
7159
|
+
}
|
|
7160
|
+
params.push(safeLimit);
|
|
7161
|
+
const sql = `
|
|
7162
|
+
SELECT id, tenant_id, topic, aggregate_type, aggregate_id, attempts, claimed_at, processed_at, last_error, payload_json
|
|
7163
|
+
FROM outbox
|
|
7164
|
+
${where}
|
|
7165
|
+
ORDER BY id DESC
|
|
7166
|
+
LIMIT $${params.length}
|
|
7167
|
+
`;
|
|
7168
|
+
const res = await client.query(sql, params);
|
|
7169
|
+
return res.rows;
|
|
7170
|
+
});
|
|
6405
7171
|
};
|
|
6406
7172
|
|
|
6407
7173
|
store.close = async function close() {
|