xytara 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +29 -0
- package/BSV_TERANODE_SETUP.md +43 -0
- package/README.md +66 -7
- package/REAL_PAYMENT_SETUP.md +61 -0
- package/RELEASE_NOTES.md +12 -6
- package/SERVICE_CONTRACT.md +31 -0
- package/START_HERE.md +107 -6
- package/TREASURY_DESTINATIONS.example.json +66 -0
- package/TREASURY_DESTINATIONS.production.template.json +108 -0
- package/adapters/examples/langchain-reference-execution-adapter.js +81 -0
- package/adapters/examples/langchain-reference-execution-adapter.manifest.json +79 -0
- package/adapters/examples/langgraph-reference-execution-adapter.js +81 -0
- package/adapters/examples/langgraph-reference-execution-adapter.manifest.json +79 -0
- package/bin/xytara-first-run.js +244 -0
- package/bin/xytara.js +30 -5
- package/integrations/dispatch.js +7 -2
- package/integrations/registry.js +123 -2
- package/lib/activity_audit_contract.js +322 -0
- package/lib/adapter_depth_contract.js +156 -0
- package/lib/bolt_lane_contract.js +55 -0
- package/lib/bolt_payment_front.js +172 -0
- package/lib/capability_execution_truth.js +269 -0
- package/lib/capability_registry.js +39 -2
- package/lib/checkout_event_security.js +142 -0
- package/lib/command_flow.js +169 -36
- package/lib/commerce_artifacts.js +23 -4
- package/lib/commerce_checkout.js +134 -4
- package/lib/commerce_client.js +20 -0
- package/lib/commerce_economics.js +109 -9
- package/lib/commerce_runtime.js +38 -2
- package/lib/commerce_shell.js +4 -2
- package/lib/external_execution_runtime.js +132 -0
- package/lib/go_live_operator_pack.js +339 -0
- package/lib/http_transport.js +32 -0
- package/lib/integration_matrix_contract.js +6 -0
- package/lib/l402_lane_contract.js +55 -0
- package/lib/l402_payment_front.js +192 -0
- package/lib/network_transport.js +110 -0
- package/lib/operator_intelligence.js +215 -1
- package/lib/payment_config.js +80 -3
- package/lib/payment_front_registry.js +53 -0
- package/lib/payment_protocol_contract.js +165 -0
- package/lib/payment_verification.js +23 -3
- package/lib/pricing_optimization_contract.js +556 -0
- package/lib/release_history.js +15 -0
- package/lib/request_rate_limit.js +144 -0
- package/lib/runtime_bridge.js +10 -2
- package/lib/runtime_operations_worker.js +282 -0
- package/lib/settlement_bsv_live.js +79 -4
- package/lib/stripe_mpp_lane_contract.js +3 -0
- package/lib/stripe_mpp_payment_front.js +158 -0
- package/lib/treasury_destinations_contract.js +543 -0
- package/lib/treasury_egress_policy_contract.js +91 -0
- package/package.json +14 -21
- package/server.js +627 -88
- package/OPERATIONS_RUNBOOK.md +0 -66
- package/OPERATOR_START_HERE.md +0 -63
- package/PROGRAM_COMPLETE_RELEASE.md +0 -63
- package/PROGRAM_STATUS.md +0 -57
- package/PUBLISH_PLAN.md +0 -14
- package/RELEASE_CHECKLIST.md +0 -16
package/lib/commerce_client.js
CHANGED
|
@@ -1499,6 +1499,26 @@ function summarizeDeliveryPayload(payload) {
|
|
|
1499
1499
|
status: delivery.status || null,
|
|
1500
1500
|
attempt_count: Number(delivery.attempt_count || 0),
|
|
1501
1501
|
last_attempt_at_iso: delivery.last_attempt_at_iso || null,
|
|
1502
|
+
callback_delivery_status: delivery.callback_delivery && delivery.callback_delivery.status
|
|
1503
|
+
? delivery.callback_delivery.status
|
|
1504
|
+
: null,
|
|
1505
|
+
callback_delivery_attempt_count: Number(
|
|
1506
|
+
delivery.callback_delivery && delivery.callback_delivery.attempt_count
|
|
1507
|
+
? delivery.callback_delivery.attempt_count
|
|
1508
|
+
: 0
|
|
1509
|
+
),
|
|
1510
|
+
callback_next_attempt_at_iso: delivery.callback_delivery && delivery.callback_delivery.next_attempt_at_iso
|
|
1511
|
+
? delivery.callback_delivery.next_attempt_at_iso
|
|
1512
|
+
: null,
|
|
1513
|
+
callback_delivered_at_iso: delivery.callback_delivery && delivery.callback_delivery.delivered_at_iso
|
|
1514
|
+
? delivery.callback_delivery.delivered_at_iso
|
|
1515
|
+
: null,
|
|
1516
|
+
callback_failed_at_iso: delivery.callback_delivery && delivery.callback_delivery.failed_at_iso
|
|
1517
|
+
? delivery.callback_delivery.failed_at_iso
|
|
1518
|
+
: null,
|
|
1519
|
+
callback_last_error: delivery.callback_delivery && delivery.callback_delivery.last_error
|
|
1520
|
+
? delivery.callback_delivery.last_error
|
|
1521
|
+
: null,
|
|
1502
1522
|
created_at_iso: delivery.created_at_iso || null,
|
|
1503
1523
|
delivered_at_iso: delivery.delivered_at_iso || null,
|
|
1504
1524
|
history_count: Array.isArray(delivery.history) ? delivery.history.length : 0
|
|
@@ -226,6 +226,8 @@ function applyRailCredits(state, body) {
|
|
|
226
226
|
currency: normalizeString(payload.currency, null),
|
|
227
227
|
purchase_intent_id: normalizeString(payload.purchase_intent_id, null),
|
|
228
228
|
provider_reference: normalizeString(payload.provider_reference, null),
|
|
229
|
+
normalized_event_kind: "mint",
|
|
230
|
+
reverses_credit_event_id: null,
|
|
229
231
|
created_at_iso: nowIso(),
|
|
230
232
|
applied_status: "recorded"
|
|
231
233
|
};
|
|
@@ -233,6 +235,92 @@ function applyRailCredits(state, body) {
|
|
|
233
235
|
return event;
|
|
234
236
|
}
|
|
235
237
|
|
|
238
|
+
function reverseRailCredits(state, body) {
|
|
239
|
+
const payload = ensureObject(body);
|
|
240
|
+
const accountId = normalizeString(payload.account_id, "acct_demo");
|
|
241
|
+
const originalCreditEventId = normalizeString(payload.credit_event_id, null);
|
|
242
|
+
const purchaseIntentId = normalizeString(payload.purchase_intent_id, null);
|
|
243
|
+
const providerReference = normalizeString(payload.provider_reference, null);
|
|
244
|
+
const credits = ensureArray(state.economicsRailCredits);
|
|
245
|
+
const original = credits.find((entry) => {
|
|
246
|
+
if (!entry || entry.account_id !== accountId || entry.normalized_event_kind === "reverse") return false;
|
|
247
|
+
if (originalCreditEventId && entry.credit_event_id === originalCreditEventId) return true;
|
|
248
|
+
if (purchaseIntentId && entry.purchase_intent_id === purchaseIntentId) return true;
|
|
249
|
+
if (providerReference && entry.provider_reference === providerReference) return true;
|
|
250
|
+
return false;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (!original) {
|
|
254
|
+
return {
|
|
255
|
+
ok: false,
|
|
256
|
+
reason: "credit_event_not_found",
|
|
257
|
+
account_id: accountId,
|
|
258
|
+
credit_event_id: originalCreditEventId,
|
|
259
|
+
purchase_intent_id: purchaseIntentId
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const existingReversal = credits.find((entry) =>
|
|
264
|
+
entry
|
|
265
|
+
&& entry.account_id === accountId
|
|
266
|
+
&& entry.normalized_event_kind === "reverse"
|
|
267
|
+
&& entry.reverses_credit_event_id === original.credit_event_id
|
|
268
|
+
);
|
|
269
|
+
if (existingReversal) {
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
reversal: existingReversal,
|
|
273
|
+
original,
|
|
274
|
+
already_reversed: true
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const reversalUnits = Number.isFinite(Number(payload.units))
|
|
279
|
+
? Math.max(0, Number(payload.units))
|
|
280
|
+
: Math.max(0, Number(original.units || 0));
|
|
281
|
+
const reversal = {
|
|
282
|
+
credit_event_id: normalizeString(payload.reversal_credit_event_id, `credit_reverse_${state.economicsRailCredits.length + 1}`),
|
|
283
|
+
account_id: accountId,
|
|
284
|
+
rail_kind: normalizeString(payload.rail_kind, original.rail_kind || "payment_credit"),
|
|
285
|
+
units: reversalUnits,
|
|
286
|
+
preview_status: "admissible",
|
|
287
|
+
normalized_event_kind: "reverse",
|
|
288
|
+
reverses_credit_event_id: original.credit_event_id,
|
|
289
|
+
amount_minor: Number.isFinite(Number(payload.amount_minor))
|
|
290
|
+
? Number(payload.amount_minor)
|
|
291
|
+
: Number.isFinite(Number(original.amount_minor))
|
|
292
|
+
? Number(original.amount_minor)
|
|
293
|
+
: null,
|
|
294
|
+
currency: normalizeString(payload.currency, original.currency || null),
|
|
295
|
+
purchase_intent_id: purchaseIntentId || original.purchase_intent_id || null,
|
|
296
|
+
provider_reference: providerReference || original.provider_reference || null,
|
|
297
|
+
reason: normalizeString(payload.reason, "hosted_checkout_refund_compensation"),
|
|
298
|
+
created_at_iso: nowIso(),
|
|
299
|
+
applied_status: "recorded"
|
|
300
|
+
};
|
|
301
|
+
state.economicsRailCredits = [reversal, ...credits].slice(0, 200);
|
|
302
|
+
return {
|
|
303
|
+
ok: true,
|
|
304
|
+
reversal,
|
|
305
|
+
original,
|
|
306
|
+
already_reversed: false
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function summarizeRailCreditLifecycle(credits) {
|
|
311
|
+
const allCredits = Array.isArray(credits) ? credits : [];
|
|
312
|
+
return {
|
|
313
|
+
mint_count: allCredits.filter((entry) => entry && entry.normalized_event_kind !== "reverse").length,
|
|
314
|
+
reverse_count: allCredits.filter((entry) => entry && entry.normalized_event_kind === "reverse").length,
|
|
315
|
+
minted_units: allCredits
|
|
316
|
+
.filter((entry) => entry && entry.normalized_event_kind !== "reverse")
|
|
317
|
+
.reduce((sum, entry) => sum + Number(entry.units || 0), 0),
|
|
318
|
+
reversed_units: allCredits
|
|
319
|
+
.filter((entry) => entry && entry.normalized_event_kind === "reverse")
|
|
320
|
+
.reduce((sum, entry) => sum + Number(entry.units || 0), 0)
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
236
324
|
function listEntitlementsForAccount(state, accountId) {
|
|
237
325
|
return Array.from(state.economicsEntitlements.values()).filter((item) => item && item.account_id === accountId);
|
|
238
326
|
}
|
|
@@ -493,9 +581,11 @@ function buildCreditBalanceSummary(state, accountId) {
|
|
|
493
581
|
const credits = listRailCreditsForAccount(state, accountId);
|
|
494
582
|
const allocations = listAllocationEventsForAccount(state, accountId);
|
|
495
583
|
const spends = listCreditSpendsForAccount(state, accountId);
|
|
584
|
+
const railCreditLifecycle = summarizeRailCreditLifecycle(credits);
|
|
496
585
|
const issuedUnits = allocations.filter((entry) => entry.direction === "mint").reduce((sum, entry) => sum + (entry.units || 0), 0)
|
|
497
|
-
+
|
|
498
|
-
const reversedUnits = allocations.filter((entry) => entry.direction === "reverse").reduce((sum, entry) => sum + (entry.units || 0), 0)
|
|
586
|
+
+ railCreditLifecycle.minted_units;
|
|
587
|
+
const reversedUnits = allocations.filter((entry) => entry.direction === "reverse").reduce((sum, entry) => sum + (entry.units || 0), 0)
|
|
588
|
+
+ railCreditLifecycle.reversed_units;
|
|
499
589
|
const consumedUnits = spends.reduce((sum, entry) => sum + (entry.units || 0), 0);
|
|
500
590
|
return {
|
|
501
591
|
account_id: accountId,
|
|
@@ -574,10 +664,11 @@ function buildWalletLifecycleSummary(state, accountId) {
|
|
|
574
664
|
const railCredits = listRailCreditsForAccount(state, accountId);
|
|
575
665
|
const creditSpends = listCreditSpendsForAccount(state, accountId);
|
|
576
666
|
const usageMeters = listUsageMetersForAccount(state, accountId);
|
|
667
|
+
const railCreditLifecycle = summarizeRailCreditLifecycle(railCredits);
|
|
577
668
|
const mintedUnits = allocations
|
|
578
669
|
.filter((entry) => entry && entry.direction === "mint")
|
|
579
670
|
.reduce((sum, entry) => sum + Number(entry.units || 0), 0)
|
|
580
|
-
+
|
|
671
|
+
+ railCreditLifecycle.minted_units;
|
|
581
672
|
const reservedUnits = allocations
|
|
582
673
|
.filter((entry) => entry && entry.direction === "reserve")
|
|
583
674
|
.reduce((sum, entry) => sum + Number(entry.units || 0), 0);
|
|
@@ -596,7 +687,8 @@ function buildWalletLifecycleSummary(state, accountId) {
|
|
|
596
687
|
.reduce((sum, entry) => sum + Number(entry.units || 0), 0);
|
|
597
688
|
const reversedUnits = allocations
|
|
598
689
|
.filter((entry) => entry && entry.direction === "reverse")
|
|
599
|
-
.reduce((sum, entry) => sum + Number(entry.units || 0), 0)
|
|
690
|
+
.reduce((sum, entry) => sum + Number(entry.units || 0), 0)
|
|
691
|
+
+ railCreditLifecycle.reversed_units;
|
|
600
692
|
const committedUnits = explicitCommittedUnits + spendCommittedUnits;
|
|
601
693
|
const openReservedUnits = Math.max(0, reservedUnits - releasedUnits);
|
|
602
694
|
const correctionState = reversedUnits > 0 ? "corrective_activity_present" : "none";
|
|
@@ -635,7 +727,7 @@ function buildWalletLifecycleSummary(state, accountId) {
|
|
|
635
727
|
record_interpretation: {
|
|
636
728
|
mint_sources: {
|
|
637
729
|
allocation_mint_count: allocations.filter((entry) => entry && entry.direction === "mint").length,
|
|
638
|
-
rail_credit_count:
|
|
730
|
+
rail_credit_count: railCreditLifecycle.mint_count
|
|
639
731
|
},
|
|
640
732
|
reserve_sources: {
|
|
641
733
|
allocation_reserve_count: allocations.filter((entry) => entry && entry.direction === "reserve").length
|
|
@@ -649,7 +741,8 @@ function buildWalletLifecycleSummary(state, accountId) {
|
|
|
649
741
|
allocation_release_count: allocations.filter((entry) => entry && entry.direction === "release").length
|
|
650
742
|
},
|
|
651
743
|
reverse_sources: {
|
|
652
|
-
allocation_reverse_count: allocations.filter((entry) => entry && entry.direction === "reverse").length
|
|
744
|
+
allocation_reverse_count: allocations.filter((entry) => entry && entry.direction === "reverse").length,
|
|
745
|
+
rail_credit_reverse_count: railCreditLifecycle.reverse_count
|
|
653
746
|
}
|
|
654
747
|
},
|
|
655
748
|
linked_surfaces: {
|
|
@@ -1388,9 +1481,11 @@ function buildTreasurySummary(state, accountId) {
|
|
|
1388
1481
|
const spends = listCreditSpendsForAccount(state, accountId);
|
|
1389
1482
|
const paymentLedger = listPaymentLedgerForAccount(state, accountId);
|
|
1390
1483
|
const budgets = listMapValuesByAccount(state.economicsBudgets, accountId);
|
|
1484
|
+
const railCreditLifecycle = summarizeRailCreditLifecycle(credits);
|
|
1391
1485
|
const mintedUnits = allocations.filter((entry) => entry.direction === "mint").reduce((sum, entry) => sum + (entry.units || 0), 0)
|
|
1392
|
-
+
|
|
1393
|
-
const reversedUnits = allocations.filter((entry) => entry.direction === "reverse").reduce((sum, entry) => sum + (entry.units || 0), 0)
|
|
1486
|
+
+ railCreditLifecycle.minted_units;
|
|
1487
|
+
const reversedUnits = allocations.filter((entry) => entry.direction === "reverse").reduce((sum, entry) => sum + (entry.units || 0), 0)
|
|
1488
|
+
+ railCreditLifecycle.reversed_units;
|
|
1394
1489
|
const consumedUnits = spends.reduce((sum, entry) => sum + (entry.units || 0), 0);
|
|
1395
1490
|
const settledMinor = paymentLedger.reduce((sum, entry) => sum + (entry.amount_minor || 0), 0);
|
|
1396
1491
|
const committedUnits = listTransactionsForAccount(state, accountId).length;
|
|
@@ -3596,6 +3691,7 @@ function buildNetworkParticipationPackage(state, accountId) {
|
|
|
3596
3691
|
|
|
3597
3692
|
function buildRailSummary(state, accountId) {
|
|
3598
3693
|
const credits = listRailCreditsForAccount(state, accountId);
|
|
3694
|
+
const railCreditLifecycle = summarizeRailCreditLifecycle(credits);
|
|
3599
3695
|
const paymentLedger = listPaymentLedgerForAccount(state, accountId);
|
|
3600
3696
|
const spends = listCreditSpendsForAccount(state, accountId);
|
|
3601
3697
|
const entitlements = listEntitlementsForAccount(state, accountId);
|
|
@@ -3603,7 +3699,10 @@ function buildRailSummary(state, accountId) {
|
|
|
3603
3699
|
return {
|
|
3604
3700
|
account_id: accountId,
|
|
3605
3701
|
credit_event_count: credits.length,
|
|
3606
|
-
|
|
3702
|
+
credit_mint_count: railCreditLifecycle.mint_count,
|
|
3703
|
+
credit_reverse_count: railCreditLifecycle.reverse_count,
|
|
3704
|
+
credit_units_total: railCreditLifecycle.minted_units,
|
|
3705
|
+
reversed_credit_units_total: railCreditLifecycle.reversed_units,
|
|
3607
3706
|
credit_spend_count: spends.length,
|
|
3608
3707
|
entitlement_count: entitlements.length,
|
|
3609
3708
|
low_balance_entitlement_count: replenishment.low_balance_count,
|
|
@@ -3811,6 +3910,7 @@ module.exports = {
|
|
|
3811
3910
|
recordAllocationEvent,
|
|
3812
3911
|
previewRailCredits,
|
|
3813
3912
|
applyRailCredits,
|
|
3913
|
+
reverseRailCredits,
|
|
3814
3914
|
buildCreditBalanceSummary,
|
|
3815
3915
|
buildWalletSummary,
|
|
3816
3916
|
buildWalletLifecycleSummary,
|
package/lib/commerce_runtime.js
CHANGED
|
@@ -65,6 +65,24 @@ function buildDeliveryEvent(event, delivery, atIso, extra) {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function buildCallbackDeliveryState(callbackUrl, deliveredAtIso, options = {}) {
|
|
69
|
+
if (!callbackUrl) return null;
|
|
70
|
+
const opts = options || {};
|
|
71
|
+
const initialStatus = opts.initial_status || (deliveredAtIso ? "queued" : "pending_execution");
|
|
72
|
+
return {
|
|
73
|
+
status: initialStatus,
|
|
74
|
+
attempt_count: 0,
|
|
75
|
+
max_attempts: null,
|
|
76
|
+
last_attempt_at_iso: null,
|
|
77
|
+
next_attempt_at_iso: deliveredAtIso || null,
|
|
78
|
+
delivered_at_iso: null,
|
|
79
|
+
failed_at_iso: null,
|
|
80
|
+
last_error: null,
|
|
81
|
+
last_response_status: null,
|
|
82
|
+
signature_mode: "hmac_sha256"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
68
86
|
function completePendingExecution(state, transactionId) {
|
|
69
87
|
if (!transactionId || !state.pendingExecutions.has(transactionId) || !state.transactions.has(transactionId)) {
|
|
70
88
|
return state.transactions.get(transactionId) || null;
|
|
@@ -142,7 +160,16 @@ function completePendingExecution(state, transactionId) {
|
|
|
142
160
|
buildDeliveryEvent("delivered", { ...delivery, status: "delivered", proof_ref: next.receipt.proof_ref }, completedAtIso, {
|
|
143
161
|
attempt_number: (delivery.attempt_count || 0) + 1
|
|
144
162
|
})
|
|
145
|
-
]
|
|
163
|
+
],
|
|
164
|
+
callback_delivery: delivery.callback_url
|
|
165
|
+
? {
|
|
166
|
+
...(delivery.callback_delivery || buildCallbackDeliveryState(delivery.callback_url, completedAtIso)),
|
|
167
|
+
status: "queued",
|
|
168
|
+
next_attempt_at_iso: completedAtIso,
|
|
169
|
+
last_error: null,
|
|
170
|
+
failed_at_iso: null
|
|
171
|
+
}
|
|
172
|
+
: null
|
|
146
173
|
};
|
|
147
174
|
state.deliveries.set(pending.deliveryId, nextDelivery);
|
|
148
175
|
}
|
|
@@ -161,7 +188,8 @@ function createCommandResult(state, payload, quote, options) {
|
|
|
161
188
|
const deferredCompletion = payload && payload.defer_completion === true;
|
|
162
189
|
const { transaction, receipt, command } = createCommandArtifacts(payload, quote, {
|
|
163
190
|
deferredCompletion,
|
|
164
|
-
paymentVerification: opts.paymentVerification || null
|
|
191
|
+
paymentVerification: opts.paymentVerification || null,
|
|
192
|
+
externalExecution: opts.externalExecution || null
|
|
165
193
|
});
|
|
166
194
|
const result = { transaction, receipt, command };
|
|
167
195
|
state.transactions.set(transaction.transaction_id, result);
|
|
@@ -230,6 +258,13 @@ function createCommandResult(state, payload, quote, options) {
|
|
|
230
258
|
delivered_at_iso: deferredCompletion ? null : receipt.created_at_iso,
|
|
231
259
|
attempt_count: deferredCompletion ? 0 : 1,
|
|
232
260
|
last_attempt_at_iso: deferredCompletion ? null : receipt.created_at_iso,
|
|
261
|
+
callback_delivery: buildCallbackDeliveryState(
|
|
262
|
+
transaction.callback_url,
|
|
263
|
+
deferredCompletion ? null : receipt.created_at_iso,
|
|
264
|
+
{
|
|
265
|
+
initial_status: deferredCompletion ? "pending_execution" : "queued"
|
|
266
|
+
}
|
|
267
|
+
),
|
|
233
268
|
history: [
|
|
234
269
|
buildDeliveryEvent("created", {
|
|
235
270
|
callback_url: transaction.callback_url,
|
|
@@ -263,6 +298,7 @@ function createCommandResult(state, payload, quote, options) {
|
|
|
263
298
|
}
|
|
264
299
|
|
|
265
300
|
module.exports = {
|
|
301
|
+
buildCallbackDeliveryState,
|
|
266
302
|
completePendingExecution,
|
|
267
303
|
createCommandResult,
|
|
268
304
|
createDispute,
|
package/lib/commerce_shell.js
CHANGED
|
@@ -167,8 +167,10 @@ function buildOperatorShellSummary(input) {
|
|
|
167
167
|
coordination_zone_count: Number(coordinationAdminView.zone_count || coordinationOperatorView.zone_count || 0),
|
|
168
168
|
hosted_checkout_purchase_intent_count: Number(hostedCheckoutOperatorSummary.purchase_intent_count || 0),
|
|
169
169
|
hosted_checkout_refund_review_required_count: Number(hostedCheckoutOperatorSummary.refund_review_required_count || 0),
|
|
170
|
-
hosted_checkout_execution_dispatched_count: Number(hostedCheckoutOperatorSummary.execution_dispatched_count || 0)
|
|
171
|
-
,
|
|
170
|
+
hosted_checkout_execution_dispatched_count: Number(hostedCheckoutOperatorSummary.execution_dispatched_count || 0),
|
|
171
|
+
hosted_checkout_auto_compensated_count: Number(hostedCheckoutOperatorSummary.auto_compensated_count || 0),
|
|
172
|
+
hosted_checkout_compensation_review_required_count: Number(hostedCheckoutOperatorSummary.compensation_review_required_count || 0),
|
|
173
|
+
hosted_checkout_compensation_failed_count: Number(hostedCheckoutOperatorSummary.compensation_failed_count || 0),
|
|
172
174
|
low_balance_entitlement_count: Number(entitlementReplenishmentSummary.low_balance_count || 0),
|
|
173
175
|
depleted_entitlement_count: Number(entitlementReplenishmentSummary.depleted_count || 0),
|
|
174
176
|
runtime_snapshot_enabled: runtimeDurability.enabled === true,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { buildFetchOptionsForPurpose } = require("./network_transport");
|
|
4
|
+
|
|
5
|
+
function normalizeString(value, fallback = "") {
|
|
6
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
7
|
+
return raw || fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function ensureObject(value) {
|
|
11
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadExecutionTargetRegistry() {
|
|
15
|
+
const raw = String(process.env.XYTARA_EXECUTION_TARGETS_JSON || "").trim();
|
|
16
|
+
if (!raw) return [];
|
|
17
|
+
let parsed = [];
|
|
18
|
+
try {
|
|
19
|
+
parsed = JSON.parse(raw);
|
|
20
|
+
} catch (_) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
if (!Array.isArray(parsed)) return [];
|
|
24
|
+
return parsed
|
|
25
|
+
.map((entry, index) => {
|
|
26
|
+
const item = ensureObject(entry);
|
|
27
|
+
const targetId = normalizeString(item.target_id, `execution_target_${index + 1}`);
|
|
28
|
+
const endpointUrl = normalizeString(item.endpoint_url);
|
|
29
|
+
if (!endpointUrl) return null;
|
|
30
|
+
return {
|
|
31
|
+
target_id: targetId,
|
|
32
|
+
endpoint_url: endpointUrl,
|
|
33
|
+
task_ref: normalizeString(item.task_ref) || null,
|
|
34
|
+
tool_name: normalizeString(item.tool_name) || null,
|
|
35
|
+
integration_id: normalizeString(item.integration_id) || null,
|
|
36
|
+
provider: normalizeString(item.provider, "external_execution"),
|
|
37
|
+
auth_token: normalizeString(item.auth_token) || null,
|
|
38
|
+
timeout_ms: Math.max(500, Number(item.timeout_ms || 10000) || 10000),
|
|
39
|
+
enabled: item.enabled !== false
|
|
40
|
+
};
|
|
41
|
+
})
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveExecutionTarget(payload, quote) {
|
|
46
|
+
const registry = loadExecutionTargetRegistry();
|
|
47
|
+
if (registry.length < 1) return null;
|
|
48
|
+
const body = ensureObject(payload && payload.body);
|
|
49
|
+
const taskRefs = Array.isArray(payload && payload.tasks) && payload.tasks.length > 0
|
|
50
|
+
? payload.tasks.map((task) => normalizeString(task && task.task_ref)).filter(Boolean)
|
|
51
|
+
: [normalizeString(payload && payload.task_ref)].filter(Boolean);
|
|
52
|
+
const toolName = normalizeString(body.tool_name) || null;
|
|
53
|
+
const integrationIds = new Set(
|
|
54
|
+
[]
|
|
55
|
+
.concat(normalizeString(payload && payload.integration_id) ? [normalizeString(payload && payload.integration_id)] : [])
|
|
56
|
+
.concat(Array.isArray(payload && payload.integration_ids) ? payload.integration_ids.map((value) => normalizeString(value)).filter(Boolean) : [])
|
|
57
|
+
.concat(Array.isArray(quote && quote.integration_ids) ? quote.integration_ids.map((value) => normalizeString(value)).filter(Boolean) : [])
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return registry.find((target) => {
|
|
61
|
+
if (!target.enabled) return false;
|
|
62
|
+
if (target.task_ref && !taskRefs.includes(target.task_ref)) return false;
|
|
63
|
+
if (target.tool_name && target.tool_name !== toolName) return false;
|
|
64
|
+
if (target.integration_id && !integrationIds.has(target.integration_id)) return false;
|
|
65
|
+
return true;
|
|
66
|
+
}) || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function executeExternalTarget(target, payload, quote, authorizationContext) {
|
|
70
|
+
if (!target) return null;
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const timer = setTimeout(() => controller.abort(), target.timeout_ms);
|
|
73
|
+
const body = JSON.stringify({
|
|
74
|
+
command: payload && payload.command ? payload.command : "command.execute",
|
|
75
|
+
task_ref: payload && payload.task_ref ? payload.task_ref : null,
|
|
76
|
+
tasks: Array.isArray(payload && payload.tasks) ? payload.tasks : [],
|
|
77
|
+
body: ensureObject(payload && payload.body),
|
|
78
|
+
account_id: payload && payload.account_id ? payload.account_id : null,
|
|
79
|
+
quote,
|
|
80
|
+
authorization_context: authorizationContext || null
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetch(target.endpoint_url, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: {
|
|
86
|
+
"content-type": "application/json",
|
|
87
|
+
...(target.auth_token ? { authorization: `Bearer ${target.auth_token}` } : {})
|
|
88
|
+
},
|
|
89
|
+
body,
|
|
90
|
+
signal: controller.signal,
|
|
91
|
+
...buildFetchOptionsForPurpose("external_execution")
|
|
92
|
+
});
|
|
93
|
+
const text = await response.text();
|
|
94
|
+
let parsed = null;
|
|
95
|
+
try {
|
|
96
|
+
parsed = text ? JSON.parse(text) : null;
|
|
97
|
+
} catch (_) {
|
|
98
|
+
parsed = null;
|
|
99
|
+
}
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
status: 502,
|
|
104
|
+
reason: "external_execution_target_rejected",
|
|
105
|
+
details: parsed || text || null,
|
|
106
|
+
provider: target.provider
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
ok: true,
|
|
111
|
+
provider: target.provider,
|
|
112
|
+
target_id: target.target_id,
|
|
113
|
+
target_response: parsed || null
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
status: error && error.name === "AbortError" ? 504 : 502,
|
|
119
|
+
reason: error && error.name === "AbortError" ? "external_execution_timeout" : "external_execution_failed",
|
|
120
|
+
details: error && error.message ? error.message : "external execution failed",
|
|
121
|
+
provider: target.provider
|
|
122
|
+
};
|
|
123
|
+
} finally {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
executeExternalTarget,
|
|
130
|
+
loadExecutionTargetRegistry,
|
|
131
|
+
resolveExecutionTarget
|
|
132
|
+
};
|