xytara 3.1.13 → 3.1.14

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 (55) hide show
  1. package/.env.example +8 -1
  2. package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
  3. package/RELEASE_NOTES.md +6 -4
  4. package/lib/a2a_rpc.js +280 -280
  5. package/lib/account_auth.js +585 -585
  6. package/lib/agent_identity.js +162 -162
  7. package/lib/bolt_adapter.js +187 -187
  8. package/lib/bolt_payment_front.js +224 -224
  9. package/lib/checkout_processor_adapter.js +217 -217
  10. package/lib/command_flow.js +722 -722
  11. package/lib/commerce_artifacts.js +591 -591
  12. package/lib/commerce_bridge.js +274 -274
  13. package/lib/commerce_cases.js +399 -399
  14. package/lib/commerce_client.js +7694 -7694
  15. package/lib/commerce_quotes.js +29 -0
  16. package/lib/commerce_runtime.js +322 -322
  17. package/lib/execution_dedupe.js +86 -86
  18. package/lib/external_credit_bridge.js +142 -142
  19. package/lib/http_transport.js +81 -81
  20. package/lib/l402_adapter.js +241 -241
  21. package/lib/l402_payment_front.js +265 -265
  22. package/lib/mpp_adapter.js +237 -237
  23. package/lib/payment_config.js +253 -253
  24. package/lib/payment_verification.js +357 -357
  25. package/lib/provider_payout_execution.js +155 -155
  26. package/lib/provider_registry.js +842 -842
  27. package/lib/rate_limiter.js +81 -81
  28. package/lib/runtime_state_store.js +491 -369
  29. package/lib/settlement_bsv_live.js +600 -600
  30. package/lib/settlement_bsv_teranode.js +117 -117
  31. package/lib/stripe_mpp_payment_front.js +258 -258
  32. package/lib/x402_facilitator.js +191 -191
  33. package/package.json +5 -3
  34. package/scripts/state_backup_offsite.js +196 -0
  35. package/scripts/verify_a2a_rpc.js +105 -105
  36. package/scripts/verify_agent_identity.js +153 -153
  37. package/scripts/verify_all.js +1 -1
  38. package/scripts/verify_bolt_adapter.js +128 -128
  39. package/scripts/verify_cdp_facilitator.js +110 -110
  40. package/scripts/verify_checkout_processor.js +109 -109
  41. package/scripts/verify_commerce_bridge.js +177 -177
  42. package/scripts/verify_execution_dedupe.js +189 -189
  43. package/scripts/verify_execution_surfacing.js +76 -76
  44. package/scripts/verify_final_activation.js +191 -191
  45. package/scripts/verify_l402_adapter.js +86 -86
  46. package/scripts/verify_mpp_adapter.js +128 -128
  47. package/scripts/verify_production_readiness.js +268 -268
  48. package/scripts/verify_rate_limiter.js +52 -52
  49. package/scripts/verify_refund_credit.js +106 -106
  50. package/scripts/verify_release_candidate.js +81 -81
  51. package/scripts/verify_runtime_state_guard.js +117 -0
  52. package/scripts/verify_service.js +15421 -15421
  53. package/scripts/verify_verify_execute_settle.js +208 -208
  54. package/scripts/verify_x402_facilitator.js +286 -286
  55. package/server.js +11976 -11974
@@ -1,722 +1,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, 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, 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
+ };