xytara 3.1.13 → 3.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.env.example +38 -4
  2. package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
  3. package/REAL_PAYMENT_SETUP.md +14 -7
  4. package/RELEASE_NOTES.md +9 -4
  5. package/fixtures/catalog.js +22 -0
  6. package/lib/a2a_rpc.js +287 -280
  7. package/lib/account_auth.js +585 -585
  8. package/lib/agent_identity.js +162 -162
  9. package/lib/asyncapi_contract.js +3 -3
  10. package/lib/bolt_adapter.js +187 -187
  11. package/lib/bolt_payment_front.js +224 -224
  12. package/lib/buyer_event_log.js +215 -0
  13. package/lib/buyer_safety_policy.js +132 -0
  14. package/lib/checkout_processor_adapter.js +217 -217
  15. package/lib/command_flow.js +911 -722
  16. package/lib/commerce_artifacts.js +591 -591
  17. package/lib/commerce_bridge.js +274 -274
  18. package/lib/commerce_cases.js +399 -399
  19. package/lib/commerce_client.js +7694 -7694
  20. package/lib/commerce_quotes.js +29 -0
  21. package/lib/commerce_runtime.js +322 -322
  22. package/lib/execution_dedupe.js +86 -86
  23. package/lib/external_credit_bridge.js +142 -142
  24. package/lib/http_transport.js +99 -81
  25. package/lib/l402_adapter.js +241 -241
  26. package/lib/l402_payment_front.js +265 -265
  27. package/lib/mpp_adapter.js +237 -237
  28. package/lib/openapi_contract.js +4 -0
  29. package/lib/payment_config.js +257 -253
  30. package/lib/payment_verification.js +357 -357
  31. package/lib/provider_payout_execution.js +155 -155
  32. package/lib/provider_registry.js +852 -842
  33. package/lib/rate_limiter.js +81 -81
  34. package/lib/runtime_state_store.js +495 -369
  35. package/lib/settlement_bsv_live.js +600 -600
  36. package/lib/settlement_bsv_teranode.js +117 -117
  37. package/lib/stripe_mpp_payment_front.js +258 -258
  38. package/lib/x402_facilitator.js +198 -191
  39. package/lib/x402_lane_contract.js +5 -3
  40. package/lib/x402_settlement_recovery.js +175 -0
  41. package/package.json +9 -3
  42. package/scripts/buyer_events_daily_report.js +79 -0
  43. package/scripts/state_backup_offsite.js +196 -0
  44. package/scripts/verify_a2a_rpc.js +131 -105
  45. package/scripts/verify_agent_identity.js +153 -153
  46. package/scripts/verify_all.js +1 -1
  47. package/scripts/verify_bolt_adapter.js +128 -128
  48. package/scripts/verify_buyer_event_log.js +123 -0
  49. package/scripts/verify_buyer_safety.js +141 -0
  50. package/scripts/verify_cdp_facilitator.js +110 -110
  51. package/scripts/verify_checkout_processor.js +109 -109
  52. package/scripts/verify_commerce_bridge.js +177 -177
  53. package/scripts/verify_execution_dedupe.js +189 -189
  54. package/scripts/verify_execution_surfacing.js +76 -76
  55. package/scripts/verify_final_activation.js +191 -191
  56. package/scripts/verify_l402_adapter.js +86 -86
  57. package/scripts/verify_lifecycle_spend_binding.js +88 -0
  58. package/scripts/verify_mpp_adapter.js +128 -128
  59. package/scripts/verify_production_readiness.js +268 -268
  60. package/scripts/verify_rate_limiter.js +52 -52
  61. package/scripts/verify_refund_credit.js +106 -106
  62. package/scripts/verify_release_candidate.js +81 -81
  63. package/scripts/verify_runtime_state_guard.js +117 -0
  64. package/scripts/verify_service.js +15421 -15421
  65. package/scripts/verify_verify_execute_settle.js +209 -208
  66. package/scripts/verify_x402_facilitator.js +307 -286
  67. package/scripts/verify_x402_settlement_recovery.js +153 -0
  68. package/server.js +12242 -11974
@@ -1,722 +1,911 @@
1
- "use strict";
2
-
3
- const {
4
- createCommandResult,
5
- createQuote,
6
- hydrateQuoteStates
7
- } = require("./commerce_runtime");
8
- const { consumeAccountCredits, buildUsageMeterPreview, previewCreditSpend } = require("./commerce_economics");
9
- const { resolveSpendCredential, evaluateAuthorityBindingAction } = require("./account_auth");
10
- const { validateIntegrationSelection } = require("../integrations/dispatch");
11
- const { markPaymentAccepted, settleDeferredPayment, verifyPaymentAcceptance } = require("./payment_verification");
12
- const { executeExternalTarget, resolveExecutionTarget } = require("./external_execution_runtime");
13
- const { externalExecutionBudgetMs } = require("./external_execution_policy");
14
- const { hashCanonical, issueDeliveryReceipt, loadDeliveryReceiptSigners } = require("./delivery_receipt");
15
-
16
- const DELIVERY_RECEIPT_ISSUER = "xytara-runtime";
17
- const DELIVERY_RECEIPT_RETENTION = 5000;
18
-
19
- // The payment leg of a delivery receipt: on-chain facts for x402, the spend record for credits,
20
- // and the verification mode for any other lane. Never the buyer's signature or secrets.
21
- function deliveryReceiptPayment(verification, quote) {
22
- const v = verification && typeof verification === "object" ? verification : {};
23
- const paymentPayload = v.payment_payload && typeof v.payment_payload === "object" ? v.payment_payload : {};
24
- if (v.verification_mode === "x402_facilitator") {
25
- const settlement = v.settlement && typeof v.settlement === "object" ? v.settlement : null;
26
- return {
27
- protocol: "x402",
28
- scheme: "exact",
29
- network: paymentPayload.x402_network || (settlement && settlement.network) || null,
30
- asset: paymentPayload.x402_asset || null,
31
- amount: paymentPayload.x402_amount !== undefined && paymentPayload.x402_amount !== null ? String(paymentPayload.x402_amount) : null,
32
- payer: paymentPayload.x402_payer || (settlement && settlement.payer) || null,
33
- pay_to: paymentPayload.x402_pay_to || null,
34
- settlement_tx: settlement ? (settlement.transaction || settlement.txid || null) : null,
35
- settled: Boolean(settlement && settlement.success !== false && (settlement.transaction || settlement.txid))
36
- };
37
- }
38
- if (v.verification_mode === "account_credits" || paymentPayload.credit_spend_id) {
39
- return {
40
- protocol: "account_credits",
41
- credit_spend_id: paymentPayload.credit_spend_id || null,
42
- quoted_amount_minor: quote && Number.isFinite(Number(quote.amount_minor)) ? Number(quote.amount_minor) : null,
43
- currency: (quote && quote.currency) || null
44
- };
45
- }
46
- return {
47
- protocol: v.verification_mode || "unspecified",
48
- quoted_amount_minor: quote && Number.isFinite(Number(quote.amount_minor)) ? Number(quote.amount_minor) : null,
49
- currency: (quote && quote.currency) || null
50
- };
51
- }
52
-
53
- // Issues the signed delivery receipt for a completed paid call, attaches it to the response, and
54
- // keeps it retrievable by id (bounded). Fail-safe: a receipt problem never breaks a completed call.
55
- function attachDeliveryReceipt(state, result, payload, quote, verification, externalExecutionOptions) {
56
- try {
57
- if (!result || !result.receipt) return;
58
- const execution = externalExecutionOptions && externalExecutionOptions.externalExecution
59
- ? externalExecutionOptions.externalExecution
60
- : null;
61
- const deliverables = result.command && Array.isArray(result.command.deliverables) ? result.command.deliverables : [];
62
- const outputs = deliverables.map((deliverable) => (deliverable && deliverable.output_payload !== undefined ? deliverable.output_payload : null));
63
- const body = payload && payload.body && typeof payload.body === "object" && !Array.isArray(payload.body) ? payload.body : {};
64
- const taskRef = (payload && payload.task_ref)
65
- || (Array.isArray(payload && payload.tasks) && payload.tasks[0] && payload.tasks[0].task_ref)
66
- || null;
67
- const { signers } = loadDeliveryReceiptSigners("XYTARA_DELIVERY_RECEIPT");
68
- const deliveryReceipt = issueDeliveryReceipt({
69
- receipt_id: `drcpt_${result.receipt.receipt_id.replace(/^rcpt_/, "")}`,
70
- issuer: DELIVERY_RECEIPT_ISSUER,
71
- call: {
72
- quote_id: quote && quote.quote_id ? quote.quote_id : null,
73
- transaction_id: result.transaction ? result.transaction.transaction_id : null,
74
- runtime_receipt_id: result.receipt.receipt_id,
75
- task_ref: taskRef,
76
- tool_name: typeof body.tool_name === "string" ? body.tool_name : null
77
- },
78
- payment: deliveryReceiptPayment(verification, quote),
79
- delivery: {
80
- status: "completed",
81
- provider_id: execution && execution.registered_provider_id ? execution.registered_provider_id : null,
82
- routing: execution
83
- ? (execution.registered_provider_id ? "open_provider_registry" : "operator_env_target")
84
- : "runtime_native",
85
- execution_target_id: execution && execution.target_id ? execution.target_id : null,
86
- request_sha256: hashCanonical(body),
87
- request_hash_input: "the request `body` object (e.g. { tool_name, arguments })",
88
- output_sha256: hashCanonical(outputs.length === 1 ? outputs[0] : outputs),
89
- output_hash_input: outputs.length === 1
90
- ? "command.deliverables[0].output_payload (null when the task returns no payload)"
91
- : "the array of command.deliverables[*].output_payload",
92
- completed_at: (result.receipt.timing && result.receipt.timing.completed_at_iso) || result.receipt.created_at_iso || null
93
- }
94
- }, signers);
95
- result.delivery_receipt = deliveryReceipt;
96
- if (!(state.deliveryReceipts instanceof Map)) state.deliveryReceipts = new Map();
97
- state.deliveryReceipts.set(deliveryReceipt.receipt_id, deliveryReceipt);
98
- while (state.deliveryReceipts.size > DELIVERY_RECEIPT_RETENTION) {
99
- state.deliveryReceipts.delete(state.deliveryReceipts.keys().next().value);
100
- }
101
- } catch (_) { /* non-fatal: never break a completed, possibly settled call */ }
102
- }
103
- const { resolveRegisteredTargets, hasActiveMatchingProvider, recordProviderHealth, recordProviderRouting, recordProviderSettlement } = require("./provider_registry");
104
- const { claimDeferredExecution, releaseDeferredExecution } = require("./execution_dedupe");
105
-
106
- function hasPaymentHeaders(headers, requiredHeaders) {
107
- return requiredHeaders.every((headerName) => headers[headerName]);
108
- }
109
-
110
- function parseBearerToken(headers) {
111
- const authorization = headers && typeof headers.authorization === "string"
112
- ? headers.authorization.trim()
113
- : "";
114
- const match = authorization.match(/^Bearer\s+(.+)$/i);
115
- return match ? match[1].trim() : null;
116
- }
117
-
118
- // Extract an agent-identity proof (for key-bound spend credentials) from request
119
- // headers: X-Agent-Signature (base64 ed25519 sig) + X-Agent-Timestamp (ms).
120
- function extractAgentProof(headers) {
121
- const h = headers || {};
122
- return {
123
- signature: typeof h["x-agent-signature"] === "string" ? h["x-agent-signature"] : null,
124
- timestamp: typeof h["x-agent-timestamp"] === "string" ? h["x-agent-timestamp"] : null
125
- };
126
- }
127
-
128
- function buildCreditCommitPayload(payload, quote, requiredUnits) {
129
- return {
130
- account_id: quote.account_id,
131
- agent_id: payload.agent_id || null,
132
- budget_id: payload.budget_id || null,
133
- pack_id: payload.pack_id || null,
134
- entitlement_id: payload.entitlement_id || null,
135
- quote_id: quote.quote_id,
136
- task_ref: payload.task_ref || null,
137
- workflow_ref: payload.workflow_ref || null,
138
- units: requiredUnits,
139
- reason: "credit_first_execution"
140
- };
141
- }
142
-
143
- function buildCreditRejectionResponse(creditDecision, quote) {
144
- const failureReason = creditDecision && creditDecision.credit_spend_preview
145
- ? creditDecision.credit_spend_preview.failure_reason
146
- : null;
147
- const reason = failureReason
148
- || (creditDecision && creditDecision.policy_guard && creditDecision.policy_guard.reason)
149
- || (creditDecision && creditDecision.entitlement_guard && creditDecision.entitlement_guard.reason)
150
- || (creditDecision && creditDecision.reason)
151
- || "credit_spend_not_allowed";
152
-
153
- if (reason === "insufficient_credits") {
154
- const preview = creditDecision && creditDecision.credit_spend_preview ? creditDecision.credit_spend_preview : {};
155
- return {
156
- status: 402,
157
- payload: {
158
- ok: false,
159
- reason: "insufficient_credits",
160
- insufficient_credits: {
161
- account_id: quote.account_id,
162
- required_units: preview.required_units,
163
- available_units: preview.available_units,
164
- recommended_next_surfaces: {
165
- credits_topup: "/v1/checkout/purchase-intents",
166
- direct_payment: "/v1/payment-quotes"
167
- }
168
- },
169
- quote
170
- }
171
- };
172
- }
173
-
174
- return {
175
- status: 403,
176
- payload: {
177
- ok: false,
178
- reason,
179
- account_id: quote.account_id,
180
- policy_guard: creditDecision && creditDecision.policy_guard ? creditDecision.policy_guard : null,
181
- entitlement_guard: creditDecision && creditDecision.entitlement_guard ? creditDecision.entitlement_guard : null,
182
- quote
183
- }
184
- };
185
- }
186
-
187
- // What a provider is allowed to see of the buyer's payment context: the verification outcome and
188
- // scalar payment facts only. NEVER the signed x402 authorization / deferred-settlement material
189
- // (that would hand a third-party provider the buyer's signature before settlement), and never
190
- // signature-, secret- or raw-looking keys or nested objects from any lane's payload.
191
- const SENSITIVE_CONTEXT_KEY = /signature|secret|private|authorization|raw|base64|header|token/i;
192
- function sanitizeAuthorizationContextForProvider(context) {
193
- if (!context || typeof context !== "object") return null;
194
- const payload = context.payment_payload && typeof context.payment_payload === "object" ? context.payment_payload : null;
195
- const safePayload = payload
196
- ? Object.fromEntries(Object.entries(payload).filter(([key, value]) => !SENSITIVE_CONTEXT_KEY.test(key) && (value === null || typeof value !== "object")))
197
- : null;
198
- return {
199
- ok: context.ok === true,
200
- verification_mode: context.verification_mode || null,
201
- wallet_id: context.wallet_id || null,
202
- age_seconds: Number.isFinite(Number(context.age_seconds)) ? Number(context.age_seconds) : null,
203
- payment_payload: safePayload
204
- };
205
- }
206
-
207
- async function buildExternalExecutionOptions(state, payload, quote, authorizationContext) {
208
- // One wall-clock budget for the whole call (every retry of every candidate), inside the x402
209
- // authorization window — see external_execution_policy.js.
210
- const executionOptions = { deadline_ms: Date.now() + externalExecutionBudgetMs() };
211
- const forwardedContext = sanitizeAuthorizationContextForProvider(authorizationContext);
212
- // Operator-pinned env target resolves first and is used as-is (an explicit single target,
213
- // no failover). Empty env + empty registry -> {} (unchanged behavior).
214
- const envTarget = resolveExecutionTarget(payload, quote);
215
- if (envTarget) {
216
- const execution = await executeExternalTarget(envTarget, payload, quote, forwardedContext, executionOptions);
217
- if (!execution || !execution.ok) {
218
- return { error: execution || { ok: false, status: 502, reason: "external_execution_failed" } };
219
- }
220
- return { externalExecution: execution };
221
- }
222
-
223
- // Open-network path: try health-ranked registered providers (healthy first, then half-open)
224
- // in order, FAILING OVER to the next when one is down — so a single unhealthy/throttled
225
- // provider doesn't fail the buyer when another serves the same capability. Each attempt
226
- // updates the provider's health; accounting is wrapped so it can never break execution.
227
- const candidates = resolveRegisteredTargets(state, payload, quote);
228
- if (candidates.length < 1) {
229
- // No HEALTHY candidate. If providers DO serve this capability but are all in cooldown,
230
- // fail closed (503) so the buyer is NOT charged for undelivered work — rather than falling
231
- // through to the synthetic no-op. If nothing serves it, keep the legacy local path ({}).
232
- if (hasActiveMatchingProvider(state, payload, quote)) {
233
- return { error: { ok: false, status: 503, reason: "no_healthy_provider" } };
234
- }
235
- return {};
236
- }
237
- let lastError = null;
238
- for (const target of candidates) {
239
- if (Date.now() >= executionOptions.deadline_ms) {
240
- // Budget spent on earlier candidates: stop here rather than start a call that cannot settle.
241
- lastError = lastError || { ok: false, status: 504, reason: "external_execution_budget_exhausted" };
242
- break;
243
- }
244
- const execution = await executeExternalTarget(target, payload, quote, forwardedContext, executionOptions);
245
- try { recordProviderRouting(state, target.registered_provider_id); } catch (_) { /* non-fatal */ }
246
- if (execution && execution.ok) {
247
- try { recordProviderHealth(state, target.registered_provider_id, { ok: true }); } catch (_) { /* non-fatal */ }
248
- execution.registered_provider_id = target.registered_provider_id;
249
- return { externalExecution: execution };
250
- }
251
- try { recordProviderHealth(state, target.registered_provider_id, { ok: false }); } catch (_) { /* non-fatal */ }
252
- lastError = execution || { ok: false, status: 502, reason: "external_execution_failed" };
253
- // Fail over to the NEXT provider ONLY when this one provably did NOT process the request
254
- // (rejected / unreachable). Never on an ambiguous failure (timeout, reset, 5xx) — that
255
- // could make a side-effectful provider execute twice. Health still marks the failure.
256
- if (!(execution && execution.failover_safe)) break;
257
- }
258
- return { error: lastError };
259
- }
260
-
261
- // Assets the x402 lane can settle in, with their on-chain decimals (needed to turn settled base
262
- // units into the ledger's cents). Anything else cannot be reconciled and must not accrue on the quote.
263
- const X402_ASSET_DECIMALS = new Map([
264
- ["0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", 6], // USDC, Base mainnet (eip155:8453)
265
- ["0x036cbd53842c5426634e7929541ec2318f3dcf7e", 6] // USDC, Base Sepolia (eip155:84532)
266
- ]);
267
-
268
- // The amount a provider's revenue share is computed from. The quote is what we ASKED for; on the
269
- // x402 lane the facilitator settles exactly the advertised `accepts` amount, which may differ from
270
- // the catalog quote — so accrue on min(quote, settled) and never on money that was not collected.
271
- // Other lanes bind the paid amount to the quote at verification time (local_signed) or charge the
272
- // quote directly (credits / hosted checkout), so the quote is the settled amount there.
273
- function reconcileProviderSettlementAmount(quote, verification) {
274
- const quoted = quote && Number.isFinite(Number(quote.amount_minor)) ? Math.max(0, Math.round(Number(quote.amount_minor))) : 0;
275
- const mode = verification && typeof verification.verification_mode === "string" ? verification.verification_mode : null;
276
- if (mode !== "x402_facilitator") {
277
- return { amount_minor: quoted, quoted_minor: quoted, settled_minor: null, settlement_basis: mode ? `${mode}_quote` : "quote", reconciled: true };
278
- }
279
- const paymentPayload = (verification && verification.payment_payload) || {};
280
- const decimals = X402_ASSET_DECIMALS.get(String(paymentPayload.x402_asset || "").toLowerCase());
281
- const baseUnits = /^\d{1,30}$/.test(String(paymentPayload.x402_amount)) ? Number(paymentPayload.x402_amount) : NaN;
282
- const centsCurrency = /^USD(_CENTS)?$/i.test(String((quote && quote.currency) || "USD_CENTS"));
283
- if (decimals === undefined || !Number.isFinite(baseUnits) || !centsCurrency) {
284
- return { amount_minor: 0, quoted_minor: quoted, settled_minor: null, settlement_basis: "x402_unreconciled_asset", reconciled: false };
285
- }
286
- const settledCents = Math.floor(baseUnits / Math.pow(10, decimals - 2));
287
- return { amount_minor: Math.min(quoted, settledCents), quoted_minor: quoted, settled_minor: settledCents, settlement_basis: "x402_settled_min_quote", reconciled: true };
288
- }
289
-
290
- // Records the provider revenue-share split for a settled call routed to a registered
291
- // provider. Fail-safe: metering must NEVER break or reverse a settled/charged response.
292
- function attributeProviderSettlement(state, externalExecutionOptions, payload, quote, extra, verification) {
293
- const execution = externalExecutionOptions && externalExecutionOptions.externalExecution;
294
- const providerId = execution && execution.registered_provider_id;
295
- if (!providerId) return;
296
- try {
297
- const taskRef = (payload && payload.task_ref)
298
- || (Array.isArray(payload && payload.tasks) && payload.tasks[0] && payload.tasks[0].task_ref)
299
- || null;
300
- const reconciled = reconcileProviderSettlementAmount(quote, verification);
301
- recordProviderSettlement(state, {
302
- provider_id: providerId,
303
- amount_minor: reconciled.amount_minor,
304
- quoted_minor: reconciled.quoted_minor,
305
- settled_minor: reconciled.settled_minor,
306
- settlement_basis: reconciled.settlement_basis,
307
- reconciled: reconciled.reconciled,
308
- currency: quote && quote.currency ? quote.currency : null,
309
- transaction_id: (extra && extra.transaction_id) || (quote && quote.quote_id) || null,
310
- settlement_ref: (extra && extra.settlement_ref) || null,
311
- task_ref: taskRef
312
- });
313
- } catch (_) { /* non-fatal: a settled call is never undone by an accounting error */ }
314
- }
315
-
316
- async function executeCommandRequest(state, payload, headers, options) {
317
- const opts = options || {};
318
- const requiredHeaders = Array.isArray(opts.requiredHeaders) && opts.requiredHeaders.length > 0
319
- ? opts.requiredHeaders
320
- : ["x-wallet-id", "x-payment", "x-payment-signature"];
321
- const challengePayload = typeof opts.challengePayload === "function"
322
- ? opts.challengePayload
323
- : (quote) => ({ ok: false, quote });
324
-
325
- hydrateQuoteStates(state);
326
- const validation = validateIntegrationSelection(payload);
327
- if (!validation.ok) {
328
- return {
329
- status: 400,
330
- payload: {
331
- ok: false,
332
- reason: validation.reason,
333
- errors: validation.errors,
334
- selection: validation.selection
335
- }
336
- };
337
- }
338
- const prefersCredits = payload && payload.payment_preference === "credits_first";
339
- if (!hasPaymentHeaders(headers || {}, requiredHeaders)) {
340
- const bearerToken = parseBearerToken(headers);
341
- const delegatedSpendRequested = typeof bearerToken === "string" && bearerToken.startsWith("xytara_spend_");
342
- const delegatedSpend = delegatedSpendRequested
343
- ? resolveSpendCredential(state, bearerToken, extractAgentProof(headers), {
344
- account_id: payload && payload.account_id,
345
- task_ref: payload && payload.task_ref,
346
- units: payload && payload.quoted_units,
347
- // Strict binding is scoped to this execute path (where a binding is supplied); the
348
- // lifecycle paths pass no binding and are intentionally unaffected.
349
- require: String(process.env.XYTARA_REQUIRE_SPEND_PROOF_BINDING || "").trim() === "1"
350
- })
351
- : { ok: false, reason: "spend_credential_not_requested" };
352
- if (delegatedSpendRequested && !delegatedSpend.ok) {
353
- return {
354
- status: 403,
355
- payload: {
356
- ok: false,
357
- reason: delegatedSpend.reason
358
- }
359
- };
360
- }
361
- if (delegatedSpend.ok) {
362
- // Pin the command to the credential's own account — a delegated spend token must
363
- // never be aimed at another account by supplying a different account_id.
364
- if (payload && typeof payload.account_id === "string" && payload.account_id && payload.account_id !== delegatedSpend.credential.account_id) {
365
- return { status: 403, payload: { ok: false, reason: "delegated_account_mismatch" } };
366
- }
367
- payload = {
368
- ...payload,
369
- account_id: delegatedSpend.credential.account_id,
370
- agent_id: payload && payload.agent_id ? payload.agent_id : delegatedSpend.credential.agent_id,
371
- budget_id: payload && payload.budget_id ? payload.budget_id : delegatedSpend.credential.budget_id,
372
- pack_id: delegatedSpend.credential.pack_id || (payload && payload.pack_id) || null,
373
- entitlement_id: delegatedSpend.credential.entitlement_id || (payload && payload.entitlement_id) || null
374
- };
375
- }
376
- const quote = createQuote(state, payload);
377
- if (prefersCredits) {
378
- const creditPayload = {
379
- ...payload,
380
- account_id: quote.account_id,
381
- quoted_units: Number.isFinite(Number(payload.quoted_units)) ? Number(payload.quoted_units) : undefined
382
- };
383
- const meteringPreview = buildUsageMeterPreview(state, creditPayload);
384
- const requiredUnits = meteringPreview.usage_units;
385
- if (delegatedSpend.ok && delegatedSpend.binding) {
386
- const authorityDecision = evaluateAuthorityBindingAction(delegatedSpend.binding, "delegated_credit_spend", requiredUnits);
387
- if (!authorityDecision.ok) {
388
- return {
389
- status: 403,
390
- payload: {
391
- ok: false,
392
- reason: authorityDecision.reason,
393
- binding_id: authorityDecision.binding_id || null,
394
- required_scope: authorityDecision.required_scope || null,
395
- authority_scope: authorityDecision.authority_scope || [],
396
- required_consequence_family: authorityDecision.required_consequence_family || null,
397
- allowed_consequence_families: authorityDecision.allowed_consequence_families || [],
398
- max_units: authorityDecision.max_units || null,
399
- attempted_units: authorityDecision.attempted_units || requiredUnits
400
- }
401
- };
402
- }
403
- }
404
-
405
- const creditDecision = previewCreditSpend(state, creditPayload);
406
- if (!creditDecision.ok || !creditDecision.credit_spend_preview || creditDecision.credit_spend_preview.executable_now !== true) {
407
- return buildCreditRejectionResponse(creditDecision, quote);
408
- }
409
-
410
- const preCommitAuthorizationContext = delegatedSpend.ok
411
- ? delegatedSpend.authorization_context
412
- : {
413
- ok: true,
414
- verification_mode: "account_credits",
415
- wallet_id: quote.account_id,
416
- age_seconds: 0,
417
- payment_payload: {
418
- credit_spend_status: "precommit"
419
- }
420
- };
421
- const externalExecutionOptions = await buildExternalExecutionOptions(state, payload, quote, preCommitAuthorizationContext);
422
- if (externalExecutionOptions.error) {
423
- return {
424
- status: externalExecutionOptions.error.status || 502,
425
- payload: {
426
- ok: false,
427
- reason: externalExecutionOptions.error.reason || "external_execution_failed",
428
- details: externalExecutionOptions.error.details || null,
429
- credit_spend_status: "not_committed",
430
- credit_spend_preview: creditDecision.credit_spend_preview,
431
- quote
432
- }
433
- };
434
- }
435
-
436
- const creditSpend = consumeAccountCredits(state, buildCreditCommitPayload(payload, quote, requiredUnits));
437
- if (creditSpend.ok) {
438
- const paymentVerification = {
439
- ...(delegatedSpend.ok
440
- ? delegatedSpend.authorization_context
441
- : {
442
- ok: true,
443
- verification_mode: "account_credits",
444
- wallet_id: quote.account_id,
445
- age_seconds: 0,
446
- payment_payload: {}
447
- }),
448
- payment_payload: {
449
- ...((delegatedSpend.ok && delegatedSpend.authorization_context && delegatedSpend.authorization_context.payment_payload) || {}),
450
- credit_spend_id: creditSpend.credit_spend.credit_spend_id
451
- }
452
- };
453
- attributeProviderSettlement(state, externalExecutionOptions, payload, quote, {
454
- transaction_id: creditSpend.credit_spend.credit_spend_id
455
- }, paymentVerification);
456
- const creditResult = createCommandResult(state, {
457
- ...payload,
458
- payment_preference: "credits_first"
459
- }, quote, {
460
- paymentVerification,
461
- creditSpend: creditSpend.credit_spend,
462
- skipPaymentLedger: true,
463
- externalExecution: externalExecutionOptions.externalExecution || null
464
- });
465
- attachDeliveryReceipt(state, creditResult, payload, quote, paymentVerification, externalExecutionOptions);
466
- return {
467
- status: 200,
468
- payload: creditResult
469
- };
470
- }
471
- if (creditSpend.reason !== "insufficient_credits") {
472
- return {
473
- status: 403,
474
- payload: {
475
- ok: false,
476
- reason: creditSpend.reason,
477
- account_id: quote.account_id,
478
- provider_execution_completed_but_credit_commit_failed: Boolean(externalExecutionOptions.externalExecution),
479
- policy_guard: {
480
- agent_id: creditSpend.agent_id || null,
481
- budget_id: creditSpend.budget_id || null,
482
- task_domain: creditSpend.task_domain || null,
483
- policy_ref: creditSpend.policy_ref || null,
484
- max_reserve_units: creditSpend.max_reserve_units || null,
485
- allowed_job_domains: creditSpend.allowed_job_domains || null,
486
- budget_status: creditSpend.budget_status || null,
487
- agent_status: creditSpend.agent_status || null
488
- },
489
- quote
490
- }
491
- };
492
- }
493
- return {
494
- status: 402,
495
- payload: {
496
- ok: false,
497
- reason: "insufficient_credits",
498
- provider_execution_completed_but_credit_commit_failed: Boolean(externalExecutionOptions.externalExecution),
499
- insufficient_credits: {
500
- account_id: quote.account_id,
501
- required_units: creditSpend.required_units,
502
- available_units: creditSpend.available_units,
503
- recommended_next_surfaces: {
504
- credits_topup: "/v1/checkout/purchase-intents",
505
- direct_payment: "/v1/payment-quotes"
506
- }
507
- },
508
- quote
509
- }
510
- };
511
- }
512
- return {
513
- status: 402,
514
- payload: await challengePayload(quote)
515
- };
516
- }
517
-
518
- const quote = payload.quote_id && state.quotes.get(payload.quote_id)
519
- ? state.quotes.get(payload.quote_id)
520
- : createQuote(state, payload);
521
-
522
- if (quote && quote.status === "expired") {
523
- return {
524
- status: 409,
525
- payload: {
526
- ok: false,
527
- reason: "quote_expired",
528
- quote
529
- }
530
- };
531
- }
532
-
533
- if (quote && (quote.status === "executed" || quote.status === "accepted")) {
534
- return {
535
- status: 409,
536
- payload: {
537
- ok: false,
538
- reason: "quote_already_used",
539
- quote
540
- }
541
- };
542
- }
543
-
544
- // Verify-execute-settle: for the x402 facilitator path, settlement is DEFERRED
545
- // until execution succeeds, so a failed call never settles/charges. Other paths
546
- // (native/credits, and the external rails L402/BOLT/MPP whose funds already moved
547
- // before the proof) have nothing to defer and are unaffected.
548
- const paymentVerification = await verifyPaymentAcceptance(state, headers || {}, requiredHeaders, quote, {
549
- payment_protocol: opts.paymentProtocol || (payload && payload.payment_protocol) || null,
550
- authorization_scheme: opts.authorizationScheme || null,
551
- resource_url: opts.resourceUrl || null,
552
- deferSettlement: true
553
- });
554
- if (!paymentVerification.ok) {
555
- return {
556
- status: paymentVerification.status || 403,
557
- payload: {
558
- ok: false,
559
- reason: paymentVerification.reason,
560
- ...(paymentVerification.detail ? { detail: paymentVerification.detail } : {}),
561
- quote
562
- }
563
- };
564
- }
565
-
566
- markPaymentAccepted(state, paymentVerification);
567
- const settlementDeferred = Boolean(paymentVerification.deferred_settlement);
568
-
569
- // Reserve an execution slot so the same signed x402 authorization cannot execute
570
- // twice concurrently (the deferred settle no longer fronts the on-chain nonce as an
571
- // implicit pre-execution guard). No-op for non-deferred lanes; fails open if the
572
- // store is unavailable. The slot is released in the finally below, whatever happens.
573
- const executionClaim = claimDeferredExecution(state, paymentVerification);
574
- if (!executionClaim.ok) {
575
- return {
576
- status: executionClaim.status || 409,
577
- payload: {
578
- ok: false,
579
- reason: executionClaim.reason || "payment_authorization_in_progress",
580
- ...(executionClaim.detail ? { detail: executionClaim.detail } : {}),
581
- payment_settled: false,
582
- quote
583
- }
584
- };
585
- }
586
-
587
- try {
588
- const externalExecutionOptions = await buildExternalExecutionOptions(state, payload, quote, paymentVerification);
589
- if (externalExecutionOptions.error) {
590
- // Execution failed. Report the money state by what actually happened, not by lane:
591
- // - deferred x402: we never settled, so the caller is NOT charged.
592
- // - a concrete receipt is in hand (an x402 settlement already captured): the caller
593
- // WAS charged, so surface the receipt and a refund path.
594
- // - everything else (native/local_signed, presence, and the access-token rails
595
- // whose proof we only validated): no receipt was produced on this request, so we
596
- // do not claim a charge — a bare retry hint, never a false refund pointer.
597
- const hasReceipt = Boolean(paymentVerification.settlement || paymentVerification.payment_response_base64);
598
- return {
599
- status: externalExecutionOptions.error.status || 502,
600
- payload: {
601
- ok: false,
602
- reason: externalExecutionOptions.error.reason || "external_execution_failed",
603
- details: externalExecutionOptions.error.details || null,
604
- payment_settled: hasReceipt,
605
- ...(paymentVerification.settlement ? { settlement: paymentVerification.settlement } : {}),
606
- remediation: settlementDeferred
607
- ? "execution failed before settlement; you were not charged — retry with corrected input"
608
- : hasReceipt
609
- ? "payment was accepted but execution failed; retry with corrected input or request a refund with your payment proof"
610
- : "execution failed; retry with corrected input",
611
- quote
612
- },
613
- ...(hasReceipt && paymentVerification.payment_response_base64
614
- ? { paymentResponse: paymentVerification.payment_response_base64 }
615
- : {})
616
- };
617
- }
618
-
619
- // Execution succeeded — settle the deferred payment now (no-op for non-deferred).
620
- const settleResult = await settleDeferredPayment(paymentVerification);
621
- if (!settleResult.ok) {
622
- return {
623
- status: settleResult.status || 402,
624
- payload: {
625
- ok: false,
626
- reason: settleResult.reason || "settlement_failed",
627
- ...(settleResult.detail ? { detail: settleResult.detail } : {}),
628
- payment_settled: false,
629
- remediation: "the work executed but settlement failed; you were not charged — retry",
630
- quote
631
- }
632
- };
633
- }
634
- // settleDeferredPayment is a no-op for non-deferred paths (returns {ok:true} with no
635
- // settlement) — in that case keep the verification untouched; only when a deferred
636
- // x402 settlement actually ran do we fold its receipt in and drop the deferred marker.
637
- const settlementOccurred = Boolean(settleResult.settlement || settleResult.payment_response_base64);
638
- const settledVerification = settlementOccurred
639
- ? { ...paymentVerification, settlement: settleResult.settlement, payment_response_base64: settleResult.payment_response_base64, deferred_settlement: undefined }
640
- : paymentVerification;
641
-
642
- attributeProviderSettlement(state, externalExecutionOptions, payload, quote, {
643
- settlement_ref: (settledVerification.settlement && (settledVerification.settlement.transaction || settledVerification.settlement.txid)) || null
644
- }, settledVerification);
645
- const paidResult = createCommandResult(state, payload, quote, {
646
- paymentVerification: settledVerification,
647
- externalExecution: externalExecutionOptions.externalExecution || null
648
- });
649
- attachDeliveryReceipt(state, paidResult, payload, quote, settledVerification, externalExecutionOptions);
650
- return {
651
- status: 200,
652
- payload: paidResult,
653
- ...(settledVerification.payment_response_base64
654
- ? { paymentResponse: settledVerification.payment_response_base64 }
655
- : {})
656
- };
657
- } finally {
658
- releaseDeferredExecution(state, executionClaim.key);
659
- }
660
- }
661
-
662
- async function executeAuthorizedCommandRequest(state, payload, authorizationContext) {
663
- const validation = validateIntegrationSelection(payload);
664
- if (!validation.ok) {
665
- return {
666
- status: 400,
667
- payload: {
668
- ok: false,
669
- reason: validation.reason,
670
- errors: validation.errors,
671
- selection: validation.selection
672
- }
673
- };
674
- }
675
-
676
- hydrateQuoteStates(state);
677
-
678
- const quote = createQuote(state, payload);
679
- const paymentVerification = {
680
- ok: true,
681
- verification_mode: authorizationContext && authorizationContext.verification_mode
682
- ? authorizationContext.verification_mode
683
- : "hosted_checkout",
684
- wallet_id: authorizationContext && authorizationContext.wallet_id
685
- ? authorizationContext.wallet_id
686
- : "hosted_checkout",
687
- age_seconds: 0,
688
- payment_payload: authorizationContext && authorizationContext.payment_payload
689
- ? authorizationContext.payment_payload
690
- : null
691
- };
692
- const externalExecutionOptions = await buildExternalExecutionOptions(state, payload, quote, paymentVerification);
693
- if (externalExecutionOptions.error) {
694
- return {
695
- status: externalExecutionOptions.error.status || 502,
696
- payload: {
697
- ok: false,
698
- reason: externalExecutionOptions.error.reason || "external_execution_failed",
699
- details: externalExecutionOptions.error.details || null
700
- }
701
- };
702
- }
703
-
704
- attributeProviderSettlement(state, externalExecutionOptions, payload, quote, undefined, paymentVerification);
705
- const authorizedResult = createCommandResult(state, payload, quote, {
706
- paymentVerification,
707
- externalExecution: externalExecutionOptions.externalExecution || null
708
- });
709
- attachDeliveryReceipt(state, authorizedResult, payload, quote, paymentVerification, externalExecutionOptions);
710
- return {
711
- status: 200,
712
- payload: authorizedResult
713
- };
714
- }
715
-
716
- module.exports = {
717
- executeAuthorizedCommandRequest,
718
- executeCommandRequest,
719
- buildExternalExecutionOptions,
720
- reconcileProviderSettlementAmount,
721
- sanitizeAuthorizationContextForProvider
722
- };
1
+ "use strict";
2
+
3
+ const {
4
+ createCommandResult,
5
+ createQuote,
6
+ hydrateQuoteStates
7
+ } = require("./commerce_runtime");
8
+ const { consumeAccountCredits, buildUsageMeterPreview, previewCreditSpend } = require("./commerce_economics");
9
+ const { resolveSpendCredential, evaluateAuthorityBindingAction } = require("./account_auth");
10
+ const { validateIntegrationSelection } = require("../integrations/dispatch");
11
+ const { markPaymentAccepted, settleDeferredPayment, verifyPaymentAcceptance } = require("./payment_verification");
12
+ const { executeExternalTarget, loadExecutionTargetRegistry, resolveExecutionTarget } = require("./external_execution_runtime");
13
+ const {
14
+ STATE_RESTORE_PENDING_RESPONSE,
15
+ creditSpendPolicyMode,
16
+ creditUnitPriceAtomic,
17
+ isStateRestorePending,
18
+ paidExecutionPolicyMode
19
+ } = require("./buyer_safety_policy");
20
+ const { externalExecutionBudgetMs } = require("./external_execution_policy");
21
+ const { encodePaymentResponse } = require("./x402_facilitator");
22
+ const {
23
+ authorizationRefFromVerification,
24
+ confirmAuthorizationUsed,
25
+ rememberReplay,
26
+ resolveReplay
27
+ } = require("./x402_settlement_recovery");
28
+ const { hashCanonical, issueDeliveryReceipt, loadDeliveryReceiptSigners } = require("./delivery_receipt");
29
+
30
+ const DELIVERY_RECEIPT_ISSUER = "xytara-runtime";
31
+ const DELIVERY_RECEIPT_RETENTION = 5000;
32
+
33
+ // The payment leg of a delivery receipt: on-chain facts for x402, the spend record for credits,
34
+ // and the verification mode for any other lane. Never the buyer's signature or secrets.
35
+ function deliveryReceiptPayment(verification, quote) {
36
+ const v = verification && typeof verification === "object" ? verification : {};
37
+ const paymentPayload = v.payment_payload && typeof v.payment_payload === "object" ? v.payment_payload : {};
38
+ if (v.verification_mode === "x402_facilitator") {
39
+ const settlement = v.settlement && typeof v.settlement === "object" ? v.settlement : null;
40
+ return {
41
+ protocol: "x402",
42
+ scheme: "exact",
43
+ network: paymentPayload.x402_network || (settlement && settlement.network) || null,
44
+ asset: paymentPayload.x402_asset || null,
45
+ amount: paymentPayload.x402_amount !== undefined && paymentPayload.x402_amount !== null ? String(paymentPayload.x402_amount) : null,
46
+ payer: paymentPayload.x402_payer || (settlement && settlement.payer) || null,
47
+ pay_to: paymentPayload.x402_pay_to || null,
48
+ settlement_tx: settlement ? (settlement.transaction || settlement.txid || null) : null,
49
+ settled: Boolean(settlement && settlement.success !== false && (settlement.transaction || settlement.txid))
50
+ };
51
+ }
52
+ if (v.verification_mode === "account_credits" || paymentPayload.credit_spend_id) {
53
+ return {
54
+ protocol: "account_credits",
55
+ credit_spend_id: paymentPayload.credit_spend_id || null,
56
+ quoted_amount_minor: quote && Number.isFinite(Number(quote.amount_minor)) ? Number(quote.amount_minor) : null,
57
+ currency: (quote && quote.currency) || null
58
+ };
59
+ }
60
+ return {
61
+ protocol: v.verification_mode || "unspecified",
62
+ quoted_amount_minor: quote && Number.isFinite(Number(quote.amount_minor)) ? Number(quote.amount_minor) : null,
63
+ currency: (quote && quote.currency) || null
64
+ };
65
+ }
66
+
67
+ // Issues the signed delivery receipt for a completed paid call, attaches it to the response, and
68
+ // keeps it retrievable by id (bounded). Fail-safe: a receipt problem never breaks a completed call.
69
+ function attachDeliveryReceipt(state, result, payload, quote, verification, externalExecutionOptions) {
70
+ try {
71
+ if (!result || !result.receipt) return;
72
+ const execution = externalExecutionOptions && externalExecutionOptions.externalExecution
73
+ ? externalExecutionOptions.externalExecution
74
+ : null;
75
+ const deliverables = result.command && Array.isArray(result.command.deliverables) ? result.command.deliverables : [];
76
+ const outputs = deliverables.map((deliverable) => (deliverable && deliverable.output_payload !== undefined ? deliverable.output_payload : null));
77
+ const body = payload && payload.body && typeof payload.body === "object" && !Array.isArray(payload.body) ? payload.body : {};
78
+ const taskRef = (payload && payload.task_ref)
79
+ || (Array.isArray(payload && payload.tasks) && payload.tasks[0] && payload.tasks[0].task_ref)
80
+ || null;
81
+ const { signers } = loadDeliveryReceiptSigners("XYTARA_DELIVERY_RECEIPT");
82
+ const deliveryReceipt = issueDeliveryReceipt({
83
+ receipt_id: `drcpt_${result.receipt.receipt_id.replace(/^rcpt_/, "")}`,
84
+ issuer: DELIVERY_RECEIPT_ISSUER,
85
+ call: {
86
+ quote_id: quote && quote.quote_id ? quote.quote_id : null,
87
+ transaction_id: result.transaction ? result.transaction.transaction_id : null,
88
+ runtime_receipt_id: result.receipt.receipt_id,
89
+ task_ref: taskRef,
90
+ tool_name: typeof body.tool_name === "string" ? body.tool_name : null
91
+ },
92
+ payment: deliveryReceiptPayment(verification, quote),
93
+ delivery: {
94
+ status: "completed",
95
+ provider_id: execution && execution.registered_provider_id ? execution.registered_provider_id : null,
96
+ routing: execution
97
+ ? (execution.registered_provider_id ? "open_provider_registry" : "operator_env_target")
98
+ : "runtime_native",
99
+ execution_target_id: execution && execution.target_id ? execution.target_id : null,
100
+ request_sha256: hashCanonical(body),
101
+ request_hash_input: "the request `body` object (e.g. { tool_name, arguments })",
102
+ output_sha256: hashCanonical(outputs.length === 1 ? outputs[0] : outputs),
103
+ output_hash_input: outputs.length === 1
104
+ ? "command.deliverables[0].output_payload (null when the task returns no payload)"
105
+ : "the array of command.deliverables[*].output_payload",
106
+ completed_at: (result.receipt.timing && result.receipt.timing.completed_at_iso) || result.receipt.created_at_iso || null
107
+ }
108
+ }, signers);
109
+ result.delivery_receipt = deliveryReceipt;
110
+ if (!(state.deliveryReceipts instanceof Map)) state.deliveryReceipts = new Map();
111
+ state.deliveryReceipts.set(deliveryReceipt.receipt_id, deliveryReceipt);
112
+ while (state.deliveryReceipts.size > DELIVERY_RECEIPT_RETENTION) {
113
+ state.deliveryReceipts.delete(state.deliveryReceipts.keys().next().value);
114
+ }
115
+ } catch (_) { /* non-fatal: never break a completed, possibly settled call */ }
116
+ }
117
+ const { resolveRegisteredTargets, hasActiveMatchingProvider, recordProviderHealth, recordProviderRouting, recordProviderSettlement } = require("./provider_registry");
118
+ const { claimDeferredExecution, releaseDeferredExecution } = require("./execution_dedupe");
119
+
120
+ function hasPaymentHeaders(headers, requiredHeaders) {
121
+ return requiredHeaders.every((headerName) => headers[headerName]);
122
+ }
123
+
124
+ function parseBearerToken(headers) {
125
+ const authorization = headers && typeof headers.authorization === "string"
126
+ ? headers.authorization.trim()
127
+ : "";
128
+ const match = authorization.match(/^Bearer\s+(.+)$/i);
129
+ return match ? match[1].trim() : null;
130
+ }
131
+
132
+ // Extract an agent-identity proof (for key-bound spend credentials) from request
133
+ // headers: X-Agent-Signature (base64 ed25519 sig) + X-Agent-Timestamp (ms).
134
+ function extractAgentProof(headers) {
135
+ const h = headers || {};
136
+ return {
137
+ signature: typeof h["x-agent-signature"] === "string" ? h["x-agent-signature"] : null,
138
+ timestamp: typeof h["x-agent-timestamp"] === "string" ? h["x-agent-timestamp"] : null
139
+ };
140
+ }
141
+
142
+ function buildCreditCommitPayload(payload, quote, requiredUnits) {
143
+ return {
144
+ account_id: quote.account_id,
145
+ agent_id: payload.agent_id || null,
146
+ budget_id: payload.budget_id || null,
147
+ pack_id: payload.pack_id || null,
148
+ entitlement_id: payload.entitlement_id || null,
149
+ quote_id: quote.quote_id,
150
+ task_ref: payload.task_ref || null,
151
+ workflow_ref: payload.workflow_ref || null,
152
+ units: requiredUnits,
153
+ reason: "credit_first_execution"
154
+ };
155
+ }
156
+
157
+ function buildCreditRejectionResponse(creditDecision, quote) {
158
+ const failureReason = creditDecision && creditDecision.credit_spend_preview
159
+ ? creditDecision.credit_spend_preview.failure_reason
160
+ : null;
161
+ const reason = failureReason
162
+ || (creditDecision && creditDecision.policy_guard && creditDecision.policy_guard.reason)
163
+ || (creditDecision && creditDecision.entitlement_guard && creditDecision.entitlement_guard.reason)
164
+ || (creditDecision && creditDecision.reason)
165
+ || "credit_spend_not_allowed";
166
+
167
+ if (reason === "insufficient_credits") {
168
+ const preview = creditDecision && creditDecision.credit_spend_preview ? creditDecision.credit_spend_preview : {};
169
+ return {
170
+ status: 402,
171
+ payload: {
172
+ ok: false,
173
+ reason: "insufficient_credits",
174
+ insufficient_credits: {
175
+ account_id: quote.account_id,
176
+ required_units: preview.required_units,
177
+ available_units: preview.available_units,
178
+ recommended_next_surfaces: {
179
+ credits_topup: "/x402/credits/topup",
180
+ direct_payment: "/v1/payment-quotes"
181
+ }
182
+ },
183
+ quote
184
+ }
185
+ };
186
+ }
187
+
188
+ return {
189
+ status: 403,
190
+ payload: {
191
+ ok: false,
192
+ reason,
193
+ account_id: quote.account_id,
194
+ policy_guard: creditDecision && creditDecision.policy_guard ? creditDecision.policy_guard : null,
195
+ entitlement_guard: creditDecision && creditDecision.entitlement_guard ? creditDecision.entitlement_guard : null,
196
+ quote
197
+ }
198
+ };
199
+ }
200
+
201
+ // What a provider is allowed to see of the buyer's payment context: the verification outcome and
202
+ // scalar payment facts only. NEVER the signed x402 authorization / deferred-settlement material
203
+ // (that would hand a third-party provider the buyer's signature before settlement), and never
204
+ // signature-, secret- or raw-looking keys or nested objects from any lane's payload.
205
+ const SENSITIVE_CONTEXT_KEY = /signature|secret|private|authorization|raw|base64|header|token/i;
206
+ function sanitizeAuthorizationContextForProvider(context) {
207
+ if (!context || typeof context !== "object") return null;
208
+ const payload = context.payment_payload && typeof context.payment_payload === "object" ? context.payment_payload : null;
209
+ const safePayload = payload
210
+ ? Object.fromEntries(Object.entries(payload).filter(([key, value]) => !SENSITIVE_CONTEXT_KEY.test(key) && (value === null || typeof value !== "object")))
211
+ : null;
212
+ return {
213
+ ok: context.ok === true,
214
+ verification_mode: context.verification_mode || null,
215
+ wallet_id: context.wallet_id || null,
216
+ age_seconds: Number.isFinite(Number(context.age_seconds)) ? Number(context.age_seconds) : null,
217
+ payment_payload: safePayload
218
+ };
219
+ }
220
+
221
+ // The execution deadline: the configured budget, but never so late that the buyer's signed x402
222
+ // authorization expires before we can settle it (keep 20 s for the settle call).
223
+ function executionDeadlineMs(authorizationContext) {
224
+ const budgetDeadline = Date.now() + externalExecutionBudgetMs();
225
+ const deferred = authorizationContext && authorizationContext.deferred_settlement;
226
+ const signed = deferred && deferred.authorization && deferred.authorization.paymentPayload
227
+ && deferred.authorization.paymentPayload.payload && deferred.authorization.paymentPayload.payload.authorization;
228
+ const validBeforeSeconds = signed ? Number(signed.validBefore) : NaN;
229
+ if (!Number.isFinite(validBeforeSeconds) || validBeforeSeconds <= 0) return budgetDeadline;
230
+ return Math.min(budgetDeadline, validBeforeSeconds * 1000 - 20000);
231
+ }
232
+
233
+ async function buildExternalExecutionOptions(state, payload, quote, authorizationContext) {
234
+ // One wall-clock budget for the whole call (every retry of every candidate), inside the x402
235
+ // authorization window — see external_execution_policy.js.
236
+ const executionOptions = { deadline_ms: executionDeadlineMs(authorizationContext) };
237
+ const forwardedContext = sanitizeAuthorizationContextForProvider(authorizationContext);
238
+ // Operator-pinned env target resolves first and is used as-is (an explicit single target,
239
+ // no failover). Empty env + empty registry -> {} (unchanged behavior).
240
+ const envTarget = resolveExecutionTarget(payload, quote);
241
+ if (envTarget) {
242
+ const execution = await executeExternalTarget(envTarget, payload, quote, forwardedContext, executionOptions);
243
+ if (!execution || !execution.ok) {
244
+ return { error: execution || { ok: false, status: 502, reason: "external_execution_failed" } };
245
+ }
246
+ return { externalExecution: execution };
247
+ }
248
+
249
+ // Open-network path: try health-ranked registered providers (healthy first, then half-open)
250
+ // in order, FAILING OVER to the next when one is down — so a single unhealthy/throttled
251
+ // provider doesn't fail the buyer when another serves the same capability. Each attempt
252
+ // updates the provider's health; accounting is wrapped so it can never break execution.
253
+ const candidates = resolveRegisteredTargets(state, payload, quote);
254
+ if (candidates.length < 1) {
255
+ // No HEALTHY candidate. If providers DO serve this capability but are all in cooldown,
256
+ // fail closed (503) so the buyer is NOT charged for undelivered work — rather than falling
257
+ // through to the synthetic no-op. If nothing serves it, keep the legacy local path ({}).
258
+ if (hasActiveMatchingProvider(state, payload, quote)) {
259
+ return { error: { ok: false, status: 503, reason: "no_healthy_provider" } };
260
+ }
261
+ // Defense in depth behind assessPaidExecutability: on a hosted deploy a call with no real
262
+ // execution target must never reach settlement with a synthetic result.
263
+ if (paidExecutionPolicyMode() === "strict") {
264
+ return { error: { ok: false, status: 501, reason: "capability_not_executable" } };
265
+ }
266
+ return {};
267
+ }
268
+ let lastError = null;
269
+ for (const target of candidates) {
270
+ if (Date.now() >= executionOptions.deadline_ms) {
271
+ // Budget spent on earlier candidates: stop here rather than start a call that cannot settle.
272
+ lastError = lastError || { ok: false, status: 504, reason: "external_execution_budget_exhausted" };
273
+ break;
274
+ }
275
+ const execution = await executeExternalTarget(target, payload, quote, forwardedContext, executionOptions);
276
+ try { recordProviderRouting(state, target.registered_provider_id); } catch (_) { /* non-fatal */ }
277
+ if (execution && execution.ok) {
278
+ try { recordProviderHealth(state, target.registered_provider_id, { ok: true }); } catch (_) { /* non-fatal */ }
279
+ execution.registered_provider_id = target.registered_provider_id;
280
+ return { externalExecution: execution };
281
+ }
282
+ try { recordProviderHealth(state, target.registered_provider_id, { ok: false }); } catch (_) { /* non-fatal */ }
283
+ lastError = execution || { ok: false, status: 502, reason: "external_execution_failed" };
284
+ // Fail over to the NEXT provider ONLY when this one provably did NOT process the request
285
+ // (rejected / unreachable). Never on an ambiguous failure (timeout, reset, 5xx) — that
286
+ // could make a side-effectful provider execute twice. Health still marks the failure.
287
+ if (!(execution && execution.failover_safe)) break;
288
+ }
289
+ return { error: lastError };
290
+ }
291
+
292
+ // Assets the x402 lane can settle in, with their on-chain decimals (needed to turn settled base
293
+ // units into the ledger's cents). Anything else cannot be reconciled and must not accrue on the quote.
294
+ const X402_ASSET_DECIMALS = new Map([
295
+ ["0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", 6], // USDC, Base mainnet (eip155:8453)
296
+ ["0x036cbd53842c5426634e7929541ec2318f3dcf7e", 6] // USDC, Base Sepolia (eip155:84532)
297
+ ]);
298
+
299
+ // The amount a provider's revenue share is computed from. The quote is what we ASKED for; on the
300
+ // x402 lane the facilitator settles exactly the advertised `accepts` amount, which may differ from
301
+ // the catalog quote — so accrue on min(quote, settled) and never on money that was not collected.
302
+ // Other lanes bind the paid amount to the quote at verification time (local_signed) or charge the
303
+ // quote directly (credits / hosted checkout), so the quote is the settled amount there.
304
+ function reconcileProviderSettlementAmount(quote, verification) {
305
+ const quoted = quote && Number.isFinite(Number(quote.amount_minor)) ? Math.max(0, Math.round(Number(quote.amount_minor))) : 0;
306
+ const mode = verification && typeof verification.verification_mode === "string" ? verification.verification_mode : null;
307
+ // Credit spends accrue on what the consumed units were actually bought for (units x unit
308
+ // price; USDC 6 decimals -> cents), never on the catalog quote.
309
+ const creditPayload = (verification && verification.payment_payload) || {};
310
+ const creditUnits = Number(creditPayload.credit_units);
311
+ const unitPriceAtomic = Number(creditPayload.credit_unit_price_atomic);
312
+ if (Number.isFinite(creditUnits) && creditUnits > 0 && Number.isFinite(unitPriceAtomic) && unitPriceAtomic > 0) {
313
+ const settledCents = Math.floor((creditUnits * unitPriceAtomic) / 10000);
314
+ return { amount_minor: Math.min(quoted, settledCents), quoted_minor: quoted, settled_minor: settledCents, settlement_basis: "credits_unit_value_min_quote", reconciled: true };
315
+ }
316
+ if (mode !== "x402_facilitator") {
317
+ return { amount_minor: quoted, quoted_minor: quoted, settled_minor: null, settlement_basis: mode ? `${mode}_quote` : "quote", reconciled: true };
318
+ }
319
+ const paymentPayload = (verification && verification.payment_payload) || {};
320
+ const decimals = X402_ASSET_DECIMALS.get(String(paymentPayload.x402_asset || "").toLowerCase());
321
+ const baseUnits = /^\d{1,30}$/.test(String(paymentPayload.x402_amount)) ? Number(paymentPayload.x402_amount) : NaN;
322
+ const centsCurrency = /^USD(_CENTS)?$/i.test(String((quote && quote.currency) || "USD_CENTS"));
323
+ if (decimals === undefined || !Number.isFinite(baseUnits) || !centsCurrency) {
324
+ return { amount_minor: 0, quoted_minor: quoted, settled_minor: null, settlement_basis: "x402_unreconciled_asset", reconciled: false };
325
+ }
326
+ const settledCents = Math.floor(baseUnits / Math.pow(10, decimals - 2));
327
+ return { amount_minor: Math.min(quoted, settledCents), quoted_minor: quoted, settled_minor: settledCents, settlement_basis: "x402_settled_min_quote", reconciled: true };
328
+ }
329
+
330
+ // Records the provider revenue-share split for a settled call routed to a registered
331
+ // provider. Fail-safe: metering must NEVER break or reverse a settled/charged response.
332
+ function attributeProviderSettlement(state, externalExecutionOptions, payload, quote, extra, verification) {
333
+ const execution = externalExecutionOptions && externalExecutionOptions.externalExecution;
334
+ const providerId = execution && execution.registered_provider_id;
335
+ if (!providerId) return;
336
+ try {
337
+ const taskRef = (payload && payload.task_ref)
338
+ || (Array.isArray(payload && payload.tasks) && payload.tasks[0] && payload.tasks[0].task_ref)
339
+ || null;
340
+ const reconciled = reconcileProviderSettlementAmount(quote, verification);
341
+ recordProviderSettlement(state, {
342
+ provider_id: providerId,
343
+ amount_minor: reconciled.amount_minor,
344
+ quoted_minor: reconciled.quoted_minor,
345
+ settled_minor: reconciled.settled_minor,
346
+ settlement_basis: reconciled.settlement_basis,
347
+ reconciled: reconciled.reconciled,
348
+ currency: quote && quote.currency ? quote.currency : null,
349
+ transaction_id: (extra && extra.transaction_id) || (quote && quote.quote_id) || null,
350
+ settlement_ref: (extra && extra.settlement_ref) || null,
351
+ task_ref: taskRef
352
+ });
353
+ } catch (_) { /* non-fatal: a settled call is never undone by an accounting error */ }
354
+ }
355
+
356
+ // When a buyer is quoted for a routed task, wake the provider(s) that will serve it: the buyer
357
+ // spends a few seconds signing the payment, which is usually enough for a sleeping free-tier host
358
+ // to come up. Fire-and-forget, at most once a minute per provider, never blocks the response.
359
+ const providerWarmedAt = new Map();
360
+ function warmProvidersForCall(state, payload, quote) {
361
+ try {
362
+ const now = Date.now();
363
+ for (const target of resolveRegisteredTargets(state, payload, quote).slice(0, 3)) {
364
+ const last = providerWarmedAt.get(target.registered_provider_id) || 0;
365
+ if (now - last < 60000) continue;
366
+ providerWarmedAt.set(target.registered_provider_id, now);
367
+ const origin = new URL(target.endpoint_url).origin;
368
+ fetch(`${origin}/health`, { method: "GET", signal: AbortSignal.timeout(3000), redirect: "error" }).catch(() => {});
369
+ }
370
+ } catch (_) { /* best effort */ }
371
+ }
372
+
373
+ // Task refs that resolve to a real execution target right now: operator-pinned env targets plus
374
+ // active open-network providers. This is the set a hosted deploy will accept payment for.
375
+ function listExecutableTaskRefs(state) {
376
+ const refs = new Set();
377
+ try {
378
+ for (const target of loadExecutionTargetRegistry()) {
379
+ if (target && target.enabled && target.task_ref) refs.add(target.task_ref);
380
+ }
381
+ } catch (_) { /* a malformed env registry simply contributes nothing */ }
382
+ const registry = state && state.providerRegistry;
383
+ if (registry && typeof registry.values === "function") {
384
+ for (const provider of registry.values()) {
385
+ if (provider && provider.status === "active" && Array.isArray(provider.task_refs)) {
386
+ provider.task_refs.forEach((ref) => refs.add(ref));
387
+ }
388
+ }
389
+ }
390
+ return Array.from(refs).sort();
391
+ }
392
+
393
+ // Refuse BEFORE quoting/charging any task that nothing can actually execute (strict policy), so
394
+ // a buyer never pays for a synthetic "completed" result. Permissive (local) keeps legacy behavior.
395
+ function assessPaidExecutability(state, payload) {
396
+ if (paidExecutionPolicyMode() !== "strict") return { ok: true };
397
+ const body = payload && typeof payload === "object" ? payload : {};
398
+ const taskRefs = Array.isArray(body.tasks) && body.tasks.length > 0
399
+ ? body.tasks.map((task) => String((task && task.task_ref) || "").trim())
400
+ : [String(body.task_ref || "").trim()];
401
+ const executable = listExecutableTaskRefs(state);
402
+ if (taskRefs.some((ref) => !ref)) {
403
+ return {
404
+ ok: false,
405
+ status: 400,
406
+ payload: { ok: false, reason: "task_ref_required", detail: "Name the task to run in task_ref.", executable_task_refs: executable }
407
+ };
408
+ }
409
+ const probeQuote = { integration_ids: Array.isArray(body.integration_ids) ? body.integration_ids : [] };
410
+ const runnable = (ref) => {
411
+ const single = { ...body, task_ref: ref, tasks: undefined };
412
+ return Boolean(resolveExecutionTarget(single, probeQuote)) || hasActiveMatchingProvider(state, single, probeQuote);
413
+ };
414
+ const missing = taskRefs.filter((ref) => !runnable(ref));
415
+ if (missing.length > 0) {
416
+ return {
417
+ ok: false,
418
+ status: 501,
419
+ payload: {
420
+ ok: false,
421
+ reason: "capability_not_executable",
422
+ detail: "Nothing can execute " + missing.join(", ") + " right now, so no payment is requested. Choose one of executable_task_refs, or call the MCP tools directly at https://xytara-mcp-executor.onrender.com/mcp.",
423
+ task_refs: missing,
424
+ executable_task_refs: executable
425
+ }
426
+ };
427
+ }
428
+ return { ok: true };
429
+ }
430
+
431
+ async function executeCommandRequest(state, payload, headers, options) {
432
+ const opts = options || {};
433
+ const requiredHeaders = Array.isArray(opts.requiredHeaders) && opts.requiredHeaders.length > 0
434
+ ? opts.requiredHeaders
435
+ : ["x-wallet-id", "x-payment", "x-payment-signature"];
436
+ const challengePayload = typeof opts.challengePayload === "function"
437
+ ? opts.challengePayload
438
+ : (quote) => ({ ok: false, quote });
439
+
440
+ hydrateQuoteStates(state);
441
+ const validation = validateIntegrationSelection(payload);
442
+ if (!validation.ok) {
443
+ return {
444
+ status: 400,
445
+ payload: {
446
+ ok: false,
447
+ reason: validation.reason,
448
+ errors: validation.errors,
449
+ selection: validation.selection
450
+ }
451
+ };
452
+ }
453
+ const executability = assessPaidExecutability(state, payload);
454
+ if (!executability.ok) {
455
+ return { status: executability.status, payload: executability.payload };
456
+ }
457
+ const prefersCredits = payload && payload.payment_preference === "credits_first";
458
+ if (prefersCredits && isStateRestorePending()) {
459
+ return { status: 503, payload: { ...STATE_RESTORE_PENDING_RESPONSE }, headers: { "Retry-After": "30" } };
460
+ }
461
+ if (!hasPaymentHeaders(headers || {}, requiredHeaders)) {
462
+ const bearerToken = parseBearerToken(headers);
463
+ const delegatedSpendRequested = typeof bearerToken === "string" && bearerToken.startsWith("xytara_spend_");
464
+ const delegatedSpend = delegatedSpendRequested
465
+ ? resolveSpendCredential(state, bearerToken, extractAgentProof(headers), {
466
+ account_id: payload && payload.account_id,
467
+ task_ref: payload && payload.task_ref,
468
+ units: payload && payload.quoted_units,
469
+ // Strict binding is scoped to this execute path (where a binding is supplied); the
470
+ // lifecycle paths pass no binding and are intentionally unaffected.
471
+ require: String(process.env.XYTARA_REQUIRE_SPEND_PROOF_BINDING || "").trim() === "1"
472
+ })
473
+ : { ok: false, reason: "spend_credential_not_requested" };
474
+ if (delegatedSpendRequested && !delegatedSpend.ok) {
475
+ return {
476
+ status: 403,
477
+ payload: {
478
+ ok: false,
479
+ reason: delegatedSpend.reason
480
+ }
481
+ };
482
+ }
483
+ if (delegatedSpend.ok) {
484
+ // Pin the command to the credential's own account — a delegated spend token must
485
+ // never be aimed at another account by supplying a different account_id.
486
+ if (payload && typeof payload.account_id === "string" && payload.account_id && payload.account_id !== delegatedSpend.credential.account_id) {
487
+ return { status: 403, payload: { ok: false, reason: "delegated_account_mismatch" } };
488
+ }
489
+ payload = {
490
+ ...payload,
491
+ account_id: delegatedSpend.credential.account_id,
492
+ agent_id: payload && payload.agent_id ? payload.agent_id : delegatedSpend.credential.agent_id,
493
+ budget_id: payload && payload.budget_id ? payload.budget_id : delegatedSpend.credential.budget_id,
494
+ pack_id: delegatedSpend.credential.pack_id || (payload && payload.pack_id) || null,
495
+ entitlement_id: delegatedSpend.credential.entitlement_id || (payload && payload.entitlement_id) || null
496
+ };
497
+ }
498
+ if (prefersCredits && !delegatedSpend.ok && creditSpendPolicyMode() === "strict") {
499
+ return {
500
+ status: 401,
501
+ payload: {
502
+ ok: false,
503
+ reason: "credit_spend_credential_required",
504
+ detail: "Spending credits needs the account's spend credential: send it as `Authorization: Bearer xytara_spend_...`. A self-serve top-up (POST /x402/credits/topup) returns one for the paying address; or pay per call with x402 instead (omit payment_preference).",
505
+ credits_topup: "/x402/credits/topup"
506
+ }
507
+ };
508
+ }
509
+ const quote = createQuote(state, payload);
510
+ if (prefersCredits) {
511
+ const creditPayload = {
512
+ ...payload,
513
+ account_id: quote.account_id,
514
+ quoted_units: Number.isFinite(Number(payload.quoted_units)) ? Number(payload.quoted_units) : undefined
515
+ };
516
+ const meteringPreview = buildUsageMeterPreview(state, creditPayload);
517
+ const requiredUnits = meteringPreview.usage_units;
518
+ if (delegatedSpend.ok && delegatedSpend.binding) {
519
+ const authorityDecision = evaluateAuthorityBindingAction(delegatedSpend.binding, "delegated_credit_spend", requiredUnits);
520
+ if (!authorityDecision.ok) {
521
+ return {
522
+ status: 403,
523
+ payload: {
524
+ ok: false,
525
+ reason: authorityDecision.reason,
526
+ binding_id: authorityDecision.binding_id || null,
527
+ required_scope: authorityDecision.required_scope || null,
528
+ authority_scope: authorityDecision.authority_scope || [],
529
+ required_consequence_family: authorityDecision.required_consequence_family || null,
530
+ allowed_consequence_families: authorityDecision.allowed_consequence_families || [],
531
+ max_units: authorityDecision.max_units || null,
532
+ attempted_units: authorityDecision.attempted_units || requiredUnits
533
+ }
534
+ };
535
+ }
536
+ }
537
+
538
+ const creditDecision = previewCreditSpend(state, creditPayload);
539
+ if (!creditDecision.ok || !creditDecision.credit_spend_preview || creditDecision.credit_spend_preview.executable_now !== true) {
540
+ return buildCreditRejectionResponse(creditDecision, quote);
541
+ }
542
+
543
+ const preCommitAuthorizationContext = delegatedSpend.ok
544
+ ? delegatedSpend.authorization_context
545
+ : {
546
+ ok: true,
547
+ verification_mode: "account_credits",
548
+ wallet_id: quote.account_id,
549
+ age_seconds: 0,
550
+ payment_payload: {
551
+ credit_spend_status: "precommit"
552
+ }
553
+ };
554
+ const externalExecutionOptions = await buildExternalExecutionOptions(state, payload, quote, preCommitAuthorizationContext);
555
+ if (externalExecutionOptions.error) {
556
+ return {
557
+ status: externalExecutionOptions.error.status || 502,
558
+ payload: {
559
+ ok: false,
560
+ reason: externalExecutionOptions.error.reason || "external_execution_failed",
561
+ details: externalExecutionOptions.error.details || null,
562
+ credit_spend_status: "not_committed",
563
+ credit_spend_preview: creditDecision.credit_spend_preview,
564
+ quote
565
+ }
566
+ };
567
+ }
568
+
569
+ const creditSpend = consumeAccountCredits(state, buildCreditCommitPayload(payload, quote, requiredUnits));
570
+ if (creditSpend.ok) {
571
+ const paymentVerification = {
572
+ ...(delegatedSpend.ok
573
+ ? delegatedSpend.authorization_context
574
+ : {
575
+ ok: true,
576
+ verification_mode: "account_credits",
577
+ wallet_id: quote.account_id,
578
+ age_seconds: 0,
579
+ payment_payload: {}
580
+ }),
581
+ payment_payload: {
582
+ ...((delegatedSpend.ok && delegatedSpend.authorization_context && delegatedSpend.authorization_context.payment_payload) || {}),
583
+ credit_spend_id: creditSpend.credit_spend.credit_spend_id,
584
+ credit_units: requiredUnits,
585
+ credit_unit_price_atomic: creditUnitPriceAtomic()
586
+ }
587
+ };
588
+ attributeProviderSettlement(state, externalExecutionOptions, payload, quote, {
589
+ transaction_id: creditSpend.credit_spend.credit_spend_id
590
+ }, paymentVerification);
591
+ const creditResult = createCommandResult(state, {
592
+ ...payload,
593
+ payment_preference: "credits_first"
594
+ }, quote, {
595
+ paymentVerification,
596
+ creditSpend: creditSpend.credit_spend,
597
+ skipPaymentLedger: true,
598
+ externalExecution: externalExecutionOptions.externalExecution || null
599
+ });
600
+ attachDeliveryReceipt(state, creditResult, payload, quote, paymentVerification, externalExecutionOptions);
601
+ return {
602
+ status: 200,
603
+ payload: creditResult
604
+ };
605
+ }
606
+ if (creditSpend.reason !== "insufficient_credits") {
607
+ return {
608
+ status: 403,
609
+ payload: {
610
+ ok: false,
611
+ reason: creditSpend.reason,
612
+ account_id: quote.account_id,
613
+ provider_execution_completed_but_credit_commit_failed: Boolean(externalExecutionOptions.externalExecution),
614
+ policy_guard: {
615
+ agent_id: creditSpend.agent_id || null,
616
+ budget_id: creditSpend.budget_id || null,
617
+ task_domain: creditSpend.task_domain || null,
618
+ policy_ref: creditSpend.policy_ref || null,
619
+ max_reserve_units: creditSpend.max_reserve_units || null,
620
+ allowed_job_domains: creditSpend.allowed_job_domains || null,
621
+ budget_status: creditSpend.budget_status || null,
622
+ agent_status: creditSpend.agent_status || null
623
+ },
624
+ quote
625
+ }
626
+ };
627
+ }
628
+ return {
629
+ status: 402,
630
+ payload: {
631
+ ok: false,
632
+ reason: "insufficient_credits",
633
+ provider_execution_completed_but_credit_commit_failed: Boolean(externalExecutionOptions.externalExecution),
634
+ insufficient_credits: {
635
+ account_id: quote.account_id,
636
+ required_units: creditSpend.required_units,
637
+ available_units: creditSpend.available_units,
638
+ recommended_next_surfaces: {
639
+ credits_topup: "/x402/credits/topup",
640
+ direct_payment: "/v1/payment-quotes"
641
+ }
642
+ },
643
+ quote
644
+ }
645
+ };
646
+ }
647
+ warmProvidersForCall(state, payload, quote);
648
+ return {
649
+ status: 402,
650
+ payload: await challengePayload(quote)
651
+ };
652
+ }
653
+
654
+ const quote = payload.quote_id && state.quotes.get(payload.quote_id)
655
+ ? state.quotes.get(payload.quote_id)
656
+ : createQuote(state, payload);
657
+
658
+ if (quote && quote.status === "expired") {
659
+ return {
660
+ status: 409,
661
+ payload: {
662
+ ok: false,
663
+ reason: "quote_expired",
664
+ quote
665
+ }
666
+ };
667
+ }
668
+
669
+ if (quote && (quote.status === "executed" || quote.status === "accepted")) {
670
+ return {
671
+ status: 409,
672
+ payload: {
673
+ ok: false,
674
+ reason: "quote_already_used",
675
+ quote
676
+ }
677
+ };
678
+ }
679
+
680
+ // A resent x402 payment we already settled (or were still confirming) gets its stored result
681
+ // instead of a re-execution or a confusing "nonce already used" failure.
682
+ const requestDigest = hashCanonical({
683
+ task_ref: (payload && payload.task_ref) || null,
684
+ tasks: (payload && payload.tasks) || null,
685
+ body: (payload && payload.body) || null
686
+ });
687
+ if (requiredHeaders.includes("payment-signature")) {
688
+ const replay = await resolveReplay(state, headers || {}, { request_digest: requestDigest });
689
+ if (replay) return replay;
690
+ }
691
+
692
+ // Verify-execute-settle: for the x402 facilitator path, settlement is DEFERRED
693
+ // until execution succeeds, so a failed call never settles/charges. Other paths
694
+ // (native/credits, and the external rails L402/BOLT/MPP whose funds already moved
695
+ // before the proof) have nothing to defer and are unaffected.
696
+ const paymentVerification = await verifyPaymentAcceptance(state, headers || {}, requiredHeaders, quote, {
697
+ payment_protocol: opts.paymentProtocol || (payload && payload.payment_protocol) || null,
698
+ authorization_scheme: opts.authorizationScheme || null,
699
+ resource_url: opts.resourceUrl || null,
700
+ deferSettlement: true
701
+ });
702
+ if (!paymentVerification.ok) {
703
+ return {
704
+ status: paymentVerification.status || 403,
705
+ payload: {
706
+ ok: false,
707
+ reason: paymentVerification.reason,
708
+ ...(paymentVerification.detail ? { detail: paymentVerification.detail } : {}),
709
+ quote
710
+ }
711
+ };
712
+ }
713
+
714
+ markPaymentAccepted(state, paymentVerification);
715
+ const settlementDeferred = Boolean(paymentVerification.deferred_settlement);
716
+
717
+ // Reserve an execution slot so the same signed x402 authorization cannot execute
718
+ // twice concurrently (the deferred settle no longer fronts the on-chain nonce as an
719
+ // implicit pre-execution guard). No-op for non-deferred lanes; fails open if the
720
+ // store is unavailable. The slot is released in the finally below, whatever happens.
721
+ const executionClaim = claimDeferredExecution(state, paymentVerification);
722
+ if (!executionClaim.ok) {
723
+ return {
724
+ status: executionClaim.status || 409,
725
+ payload: {
726
+ ok: false,
727
+ reason: executionClaim.reason || "payment_authorization_in_progress",
728
+ ...(executionClaim.detail ? { detail: executionClaim.detail } : {}),
729
+ payment_settled: false,
730
+ quote
731
+ }
732
+ };
733
+ }
734
+
735
+ try {
736
+ const externalExecutionOptions = await buildExternalExecutionOptions(state, payload, quote, paymentVerification);
737
+ if (externalExecutionOptions.error) {
738
+ // Execution failed. Report the money state by what actually happened, not by lane:
739
+ // - deferred x402: we never settled, so the caller is NOT charged.
740
+ // - a concrete receipt is in hand (an x402 settlement already captured): the caller
741
+ // WAS charged, so surface the receipt and a refund path.
742
+ // - everything else (native/local_signed, presence, and the access-token rails
743
+ // whose proof we only validated): no receipt was produced on this request, so we
744
+ // do not claim a charge — a bare retry hint, never a false refund pointer.
745
+ const hasReceipt = Boolean(paymentVerification.settlement || paymentVerification.payment_response_base64);
746
+ const failureStatus = externalExecutionOptions.error.status || 502;
747
+ // A provider outage/timeout is not the buyer's input: say so, and say when to retry.
748
+ const providerUnavailable = failureStatus === 502 || failureStatus === 503 || failureStatus === 504;
749
+ const retryHint = providerUnavailable ? "the provider did not respond in time — retry in a few seconds" : "retry with corrected input";
750
+ return {
751
+ status: failureStatus,
752
+ ...(providerUnavailable ? { headers: { "Retry-After": "5" } } : {}),
753
+ payload: {
754
+ ok: false,
755
+ reason: externalExecutionOptions.error.reason || "external_execution_failed",
756
+ details: externalExecutionOptions.error.details || null,
757
+ payment_settled: hasReceipt,
758
+ ...(paymentVerification.settlement ? { settlement: paymentVerification.settlement } : {}),
759
+ remediation: settlementDeferred
760
+ ? `execution failed before settlement; you were not charged — ${retryHint}`
761
+ : hasReceipt
762
+ ? `payment was accepted but execution failed; ${retryHint}, or request a refund with your payment proof`
763
+ : `execution failed; ${retryHint}`,
764
+ quote
765
+ },
766
+ ...(hasReceipt && paymentVerification.payment_response_base64
767
+ ? { paymentResponse: paymentVerification.payment_response_base64 }
768
+ : {})
769
+ };
770
+ }
771
+
772
+ // Execution succeeded — settle the deferred payment now (no-op for non-deferred).
773
+ const finalizePaid = (settleResult) => {
774
+ // settleDeferredPayment is a no-op for non-deferred paths (returns {ok:true} with no
775
+ // settlement) — in that case keep the verification untouched; only when a deferred
776
+ // x402 settlement actually ran do we fold its receipt in and drop the deferred marker.
777
+ const settlementOccurred = Boolean(settleResult.settlement || settleResult.payment_response_base64);
778
+ const settledVerification = settlementOccurred
779
+ ? { ...paymentVerification, settlement: settleResult.settlement, payment_response_base64: settleResult.payment_response_base64, deferred_settlement: undefined }
780
+ : paymentVerification;
781
+
782
+ attributeProviderSettlement(state, externalExecutionOptions, payload, quote, {
783
+ settlement_ref: (settledVerification.settlement && (settledVerification.settlement.transaction || settledVerification.settlement.txid)) || null
784
+ }, settledVerification);
785
+ const paidResult = createCommandResult(state, payload, quote, {
786
+ paymentVerification: settledVerification,
787
+ externalExecution: externalExecutionOptions.externalExecution || null
788
+ });
789
+ attachDeliveryReceipt(state, paidResult, payload, quote, settledVerification, externalExecutionOptions);
790
+ return {
791
+ status: 200,
792
+ payload: paidResult,
793
+ ...(settledVerification.payment_response_base64
794
+ ? { paymentResponse: settledVerification.payment_response_base64 }
795
+ : {})
796
+ };
797
+ };
798
+
799
+ let settleResult = await settleDeferredPayment(paymentVerification);
800
+ const authorizationRef = settlementDeferred ? authorizationRefFromVerification(paymentVerification) : null;
801
+ if (!settleResult.ok && authorizationRef && settleResult.reason === "facilitator_unreachable") {
802
+ // The facilitator timed out / dropped: it may still have broadcast the transfer. Ask the chain.
803
+ const used = await confirmAuthorizationUsed(authorizationRef);
804
+ if (used === true) {
805
+ const settlement = { success: true, transaction: null, network: authorizationRef.network, payer: authorizationRef.authorizer, recovered_from: "authorization_state" };
806
+ settleResult = { ok: true, settlement, payment_response_base64: encodePaymentResponse(settlement) };
807
+ } else {
808
+ // Not confirmed (yet). Keep the finished result under this payment so resending the SAME
809
+ // signature delivers it once the transfer shows up on-chain — never ask for a new payment.
810
+ rememberReplay(state, authorizationRef, {
811
+ status: "unconfirmed",
812
+ request_digest: requestDigest,
813
+ finalize: async (settlement) => finalizePaid({ ok: true, settlement, payment_response_base64: encodePaymentResponse(settlement) })
814
+ });
815
+ return {
816
+ status: 502,
817
+ payload: {
818
+ ok: false,
819
+ reason: used === false ? "settlement_not_confirmed" : "settlement_outcome_unknown",
820
+ detail: "The payment processor did not answer in time. Your work is done and held for 15 minutes: if this payment shows up on-chain, resend the SAME payment signature to receive the result; if it never settles you are not charged. Do not sign a new payment for this request.",
821
+ payment_settled: used === false ? false : null,
822
+ money_moved: used !== false,
823
+ quote
824
+ }
825
+ };
826
+ }
827
+ }
828
+ if (!settleResult.ok) {
829
+ return {
830
+ status: settleResult.status || 402,
831
+ payload: {
832
+ ok: false,
833
+ reason: settleResult.reason || "settlement_failed",
834
+ ...(settleResult.detail ? { detail: settleResult.detail } : {}),
835
+ payment_settled: false,
836
+ remediation: "the work executed but the payment was declined at settlement; you were not charged — retry with a valid payment",
837
+ quote
838
+ }
839
+ };
840
+ }
841
+ const paidResponse = finalizePaid(settleResult);
842
+ if (authorizationRef) rememberReplay(state, authorizationRef, { status: "settled", request_digest: requestDigest, response: paidResponse });
843
+ return paidResponse;
844
+ } finally {
845
+ releaseDeferredExecution(state, executionClaim.key);
846
+ }
847
+ }
848
+
849
+ async function executeAuthorizedCommandRequest(state, payload, authorizationContext) {
850
+ const validation = validateIntegrationSelection(payload);
851
+ if (!validation.ok) {
852
+ return {
853
+ status: 400,
854
+ payload: {
855
+ ok: false,
856
+ reason: validation.reason,
857
+ errors: validation.errors,
858
+ selection: validation.selection
859
+ }
860
+ };
861
+ }
862
+
863
+ hydrateQuoteStates(state);
864
+
865
+ const quote = createQuote(state, payload);
866
+ const paymentVerification = {
867
+ ok: true,
868
+ verification_mode: authorizationContext && authorizationContext.verification_mode
869
+ ? authorizationContext.verification_mode
870
+ : "hosted_checkout",
871
+ wallet_id: authorizationContext && authorizationContext.wallet_id
872
+ ? authorizationContext.wallet_id
873
+ : "hosted_checkout",
874
+ age_seconds: 0,
875
+ payment_payload: authorizationContext && authorizationContext.payment_payload
876
+ ? authorizationContext.payment_payload
877
+ : null
878
+ };
879
+ const externalExecutionOptions = await buildExternalExecutionOptions(state, payload, quote, paymentVerification);
880
+ if (externalExecutionOptions.error) {
881
+ return {
882
+ status: externalExecutionOptions.error.status || 502,
883
+ payload: {
884
+ ok: false,
885
+ reason: externalExecutionOptions.error.reason || "external_execution_failed",
886
+ details: externalExecutionOptions.error.details || null
887
+ }
888
+ };
889
+ }
890
+
891
+ attributeProviderSettlement(state, externalExecutionOptions, payload, quote, undefined, paymentVerification);
892
+ const authorizedResult = createCommandResult(state, payload, quote, {
893
+ paymentVerification,
894
+ externalExecution: externalExecutionOptions.externalExecution || null
895
+ });
896
+ attachDeliveryReceipt(state, authorizedResult, payload, quote, paymentVerification, externalExecutionOptions);
897
+ return {
898
+ status: 200,
899
+ payload: authorizedResult
900
+ };
901
+ }
902
+
903
+ module.exports = {
904
+ executeAuthorizedCommandRequest,
905
+ executeCommandRequest,
906
+ listExecutableTaskRefs,
907
+ assessPaidExecutability,
908
+ buildExternalExecutionOptions,
909
+ reconcileProviderSettlementAmount,
910
+ sanitizeAuthorizationContextForProvider
911
+ };