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.
- package/README.md +93 -3
- package/SETTLD_VERSION +1 -1
- package/bin/settld-mcp +2 -0
- package/bin/settld.js +71 -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 +152 -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 +377 -0
- package/docs/QUICKSTART_MCP_HOSTS.md +210 -0
- package/docs/QUICKSTART_POLICY_PACKS.md +65 -0
- package/docs/QUICKSTART_PRODUCE.md +61 -0
- package/docs/QUICKSTART_PROFILES.md +198 -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 +33 -0
- package/docs/RELEASE_CHECKLIST.md +182 -0
- package/docs/RELEASING.md +82 -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 +131 -0
- package/docs/SUMMARY.md +17 -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 +64 -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 +103 -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 +15 -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/PUBLIC_QUICKSTART.md +95 -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/DISPUTE_FINANCE_RECONCILIATION_PACKET.md +56 -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 +69 -0
- package/docs/ops/LIGHTHOUSE_PRODUCTION_CLOSE.md +51 -0
- package/docs/ops/MCP_COMPATIBILITY_MATRIX.md +30 -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 +140 -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 +60 -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/plans/2026-02-20-trust-os-v1-jira-backlog.md +348 -0
- package/docs/plans/2026-02-21-agent-economic-actor-operating-model.md +169 -0
- package/docs/plans/2026-02-21-trust-os-v1-strategy.md +241 -0
- package/docs/research/2026-02-21-agent-spend-host-landscape.md +57 -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/ArbitrationOutcomeMapping.v1.md +62 -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/DisputeCaseLifecycle.v1.md +51 -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/OperatorAction.v1.md +90 -0
- package/docs/spec/PRODUCER_ERRORS.md +42 -0
- package/docs/spec/PolicyDecision.v1.md +83 -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 +109 -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 +53 -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/OperatorAction.v1.schema.json +113 -0
- package/docs/spec/schemas/PolicyDecision.v1.schema.json +74 -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 +149 -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 +35 -0
- package/docs/templates/buyer-email.txt +18 -0
- package/docs/templates/buyer-one-pager.md +24 -0
- package/package.json +53 -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 +304 -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 +318 -0
- package/scripts/ci/run-10x-throughput-incident-rehearsal.mjs +368 -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-cert-matrix.mjs +201 -0
- package/scripts/ci/run-mcp-host-smoke.mjs +473 -0
- package/scripts/ci/run-offline-verification-parity-gate.mjs +762 -0
- package/scripts/ci/run-onboarding-host-success-gate.mjs +516 -0
- package/scripts/ci/run-onboarding-policy-slo-gate.mjs +537 -0
- package/scripts/ci/run-production-cutover-gate.mjs +540 -0
- package/scripts/ci/run-public-openclaw-npx-smoke.mjs +148 -0
- package/scripts/ci/run-release-promotion-guard.mjs +756 -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/doctor/mcp-host.mjs +120 -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 +1511 -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/dispute-finance-reconciliation-packet.mjs +313 -0
- package/scripts/ops/hosted-baseline-evidence.mjs +890 -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/ops/run-x402-hitl-smoke.mjs +607 -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/policy/cli.mjs +600 -0
- package/scripts/profile/cli.mjs +1324 -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/register-entity-secret.mjs +102 -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/setup/circle-bootstrap.mjs +310 -0
- package/scripts/setup/host-config.mjs +617 -0
- package/scripts/setup/onboard.mjs +1337 -0
- package/scripts/setup/openclaw-onboard.mjs +423 -0
- package/scripts/setup/wizard.mjs +986 -0
- package/scripts/slo/check.mjs +239 -0
- package/scripts/smoke/k8s-smoke.mjs +214 -0
- package/scripts/spec/generate-protocol-vectors.mjs +1019 -0
- package/scripts/test/check-no-generated-artifacts.sh +12 -0
- package/scripts/test/run.sh +59 -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 +1058 -0
- package/src/api/app.js +34658 -16940
- package/src/api/maintenance.js +70 -0
- package/src/api/middleware/trust-kernel.js +114 -0
- package/src/api/openapi.js +1778 -70
- package/src/api/persistence.js +456 -0
- package/src/api/server.js +81 -5
- package/src/api/store.js +1581 -62
- 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 +231 -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/event-policy.js +21 -2
- package/src/core/maintenance-locks.js +1 -0
- package/src/core/operator-action.js +303 -0
- package/src/core/paid-tool-manifest.js +318 -0
- package/src/core/policy-decision.js +322 -0
- package/src/core/policy-packs.js +207 -0
- package/src/core/profile-fingerprint.js +27 -0
- package/src/core/profile-simulation-reasons.js +84 -0
- package/src/core/profile-templates.js +242 -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 +239 -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/wallet-assignment-resolver.js +129 -0
- package/src/core/wallet-provider-bootstrap.js +365 -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 +1508 -111
package/src/db/store-pg.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
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
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
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
|
-
|
|
2491
|
-
|
|
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.
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
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
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
|
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 (
|
|
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() {
|