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.
Files changed (61) hide show
  1. package/.env.example +29 -0
  2. package/BSV_TERANODE_SETUP.md +43 -0
  3. package/README.md +66 -7
  4. package/REAL_PAYMENT_SETUP.md +61 -0
  5. package/RELEASE_NOTES.md +12 -6
  6. package/SERVICE_CONTRACT.md +31 -0
  7. package/START_HERE.md +107 -6
  8. package/TREASURY_DESTINATIONS.example.json +66 -0
  9. package/TREASURY_DESTINATIONS.production.template.json +108 -0
  10. package/adapters/examples/langchain-reference-execution-adapter.js +81 -0
  11. package/adapters/examples/langchain-reference-execution-adapter.manifest.json +79 -0
  12. package/adapters/examples/langgraph-reference-execution-adapter.js +81 -0
  13. package/adapters/examples/langgraph-reference-execution-adapter.manifest.json +79 -0
  14. package/bin/xytara-first-run.js +244 -0
  15. package/bin/xytara.js +30 -5
  16. package/integrations/dispatch.js +7 -2
  17. package/integrations/registry.js +123 -2
  18. package/lib/activity_audit_contract.js +322 -0
  19. package/lib/adapter_depth_contract.js +156 -0
  20. package/lib/bolt_lane_contract.js +55 -0
  21. package/lib/bolt_payment_front.js +172 -0
  22. package/lib/capability_execution_truth.js +269 -0
  23. package/lib/capability_registry.js +39 -2
  24. package/lib/checkout_event_security.js +142 -0
  25. package/lib/command_flow.js +169 -36
  26. package/lib/commerce_artifacts.js +23 -4
  27. package/lib/commerce_checkout.js +134 -4
  28. package/lib/commerce_client.js +20 -0
  29. package/lib/commerce_economics.js +109 -9
  30. package/lib/commerce_runtime.js +38 -2
  31. package/lib/commerce_shell.js +4 -2
  32. package/lib/external_execution_runtime.js +132 -0
  33. package/lib/go_live_operator_pack.js +339 -0
  34. package/lib/http_transport.js +32 -0
  35. package/lib/integration_matrix_contract.js +6 -0
  36. package/lib/l402_lane_contract.js +55 -0
  37. package/lib/l402_payment_front.js +192 -0
  38. package/lib/network_transport.js +110 -0
  39. package/lib/operator_intelligence.js +215 -1
  40. package/lib/payment_config.js +80 -3
  41. package/lib/payment_front_registry.js +53 -0
  42. package/lib/payment_protocol_contract.js +165 -0
  43. package/lib/payment_verification.js +23 -3
  44. package/lib/pricing_optimization_contract.js +556 -0
  45. package/lib/release_history.js +15 -0
  46. package/lib/request_rate_limit.js +144 -0
  47. package/lib/runtime_bridge.js +10 -2
  48. package/lib/runtime_operations_worker.js +282 -0
  49. package/lib/settlement_bsv_live.js +79 -4
  50. package/lib/stripe_mpp_lane_contract.js +3 -0
  51. package/lib/stripe_mpp_payment_front.js +158 -0
  52. package/lib/treasury_destinations_contract.js +543 -0
  53. package/lib/treasury_egress_policy_contract.js +91 -0
  54. package/package.json +14 -21
  55. package/server.js +627 -88
  56. package/OPERATIONS_RUNBOOK.md +0 -66
  57. package/OPERATOR_START_HERE.md +0 -63
  58. package/PROGRAM_COMPLETE_RELEASE.md +0 -63
  59. package/PROGRAM_STATUS.md +0 -57
  60. package/PUBLISH_PLAN.md +0 -14
  61. package/RELEASE_CHECKLIST.md +0 -16
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+
3
+ const packageJson = require("../package.json");
4
+ const {
5
+ buildCapabilityExecutionTruthBundle,
6
+ buildCapabilityCommercializationSummary
7
+ } = require("./capability_execution_truth");
8
+ const { buildPaymentProtocolStrategyPack } = require("./payment_protocol_contract");
9
+ const { summarizeTreasuryDestinationsPack } = require("./treasury_destinations_contract");
10
+ const { loadExecutionTargetRegistry } = require("./external_execution_runtime");
11
+ const { buildCheckoutEventSecuritySummary } = require("./checkout_event_security");
12
+ const {
13
+ buildOperatorIntelligencePack,
14
+ summarizeOperatorIntelligencePack
15
+ } = require("./operator_intelligence");
16
+
17
+ function normalizeFunction(fn, fallback) {
18
+ return typeof fn === "function" ? fn : fallback;
19
+ }
20
+
21
+ function buildDefaultRuntimeDurability() {
22
+ return {
23
+ ok: true,
24
+ recovery_posture: "unknown",
25
+ blockers: ["runtime_durability_unavailable"],
26
+ operator_guidance: "inspect /v1/runtime/durability from the live service to confirm persistence posture"
27
+ };
28
+ }
29
+
30
+ function buildDefaultSettlementReadiness() {
31
+ return {
32
+ ok: true,
33
+ ready: false,
34
+ runtime_mode: "unknown",
35
+ blockers: ["native_settlement_readiness_unavailable"],
36
+ next_manual_inputs: ["inspect_live_service_settlement_readiness"]
37
+ };
38
+ }
39
+
40
+ function rankBlockerSeverity(blocker) {
41
+ if (typeof blocker !== "string" || blocker.length === 0) return "medium";
42
+ if (
43
+ blocker.includes("missing")
44
+ || blocker.includes("failed")
45
+ || blocker.includes("unreachable")
46
+ || blocker.includes("not_ready")
47
+ || blocker.includes("not_writable")
48
+ || blocker.includes("mock")
49
+ || blocker.includes("too_weak")
50
+ ) {
51
+ return "high";
52
+ }
53
+ return "medium";
54
+ }
55
+
56
+ function buildBlockingIssues(executionTruth, commercialization, runtimeDurability, settlementReadiness, treasurySummary, checkoutSecurity) {
57
+ const issues = [];
58
+
59
+ (Array.isArray(runtimeDurability.blockers) ? runtimeDurability.blockers : []).forEach((blocker) => {
60
+ issues.push({
61
+ lane: "runtime_durability",
62
+ severity: rankBlockerSeverity(blocker),
63
+ blocker,
64
+ reason: runtimeDurability.operator_guidance || "runtime durability requires operator review",
65
+ ref: "/v1/runtime/durability"
66
+ });
67
+ });
68
+
69
+ (Array.isArray(settlementReadiness.blockers) ? settlementReadiness.blockers : []).forEach((blocker) => {
70
+ issues.push({
71
+ lane: "native_settlement",
72
+ severity: rankBlockerSeverity(blocker),
73
+ blocker,
74
+ reason: "native settlement readiness still has blocking configuration gaps",
75
+ ref: "/v1/settlement/bsv-teranode/readiness"
76
+ });
77
+ });
78
+
79
+ if (Number(treasurySummary.configured_profile_count || 0) < 1) {
80
+ issues.push({
81
+ lane: "treasury_destinations",
82
+ severity: "high",
83
+ blocker: "treasury_destinations_unconfigured",
84
+ reason: "no treasury destination profile is configured with a real landing reference",
85
+ ref: "/v1/treasury-destinations"
86
+ });
87
+ }
88
+
89
+ if (Number(executionTruth.counts.provider_wiring_required_count || 0) > 0) {
90
+ issues.push({
91
+ lane: "provider_execution",
92
+ severity: "high",
93
+ blocker: "provider_execution_targets_missing",
94
+ reason: `${executionTruth.counts.provider_wiring_required_count} capability lane(s) still need execution target bindings`,
95
+ ref: "/v1/capability-registry/execution-truth"
96
+ });
97
+ }
98
+
99
+ if (Number(executionTruth.counts.operator_classification_required_count || 0) > 0) {
100
+ issues.push({
101
+ lane: "capability_classification",
102
+ severity: "medium",
103
+ blocker: "capability_classification_incomplete",
104
+ reason: `${executionTruth.counts.operator_classification_required_count} capability lane(s) still require execution classification`,
105
+ ref: "/v1/capability-registry/execution-truth"
106
+ });
107
+ }
108
+
109
+ if (Number(commercialization.counts.hold_until_classified || 0) > 0) {
110
+ issues.push({
111
+ lane: "commercialization",
112
+ severity: "medium",
113
+ blocker: "catalog_items_not_ready_for_public_sale",
114
+ reason: `${commercialization.counts.hold_until_classified} capability lane(s) should stay off live public sale surfaces`,
115
+ ref: "/v1/capability-registry/commercialization-summary"
116
+ });
117
+ }
118
+
119
+ (Array.isArray(checkoutSecurity.blockers) ? checkoutSecurity.blockers : []).forEach((blocker) => {
120
+ issues.push({
121
+ lane: "hosted_checkout_ingress",
122
+ severity: rankBlockerSeverity(blocker),
123
+ blocker,
124
+ reason: checkoutSecurity.operator_guidance || "hosted checkout ingress security requires operator review",
125
+ ref: "/v1/checkout/events/security"
126
+ });
127
+ });
128
+
129
+ return issues;
130
+ }
131
+
132
+ function buildNextActions(blockingIssues, operatorIntelligence, executionTruth, treasurySummary, settlementReadiness) {
133
+ const actions = [];
134
+
135
+ if (blockingIssues.some((issue) => issue.blocker === "treasury_destinations_unconfigured")) {
136
+ actions.push({
137
+ priority: "high",
138
+ action: "configure_treasury_destinations",
139
+ reason: "real money should not land until treasury destinations are explicitly configured",
140
+ refs: [
141
+ "/v1/treasury-destinations",
142
+ "/v1/treasury-destinations/operator-pack"
143
+ ]
144
+ });
145
+ }
146
+
147
+ if (blockingIssues.some((issue) => issue.blocker === "provider_execution_targets_missing")) {
148
+ const providerRequiredTasks = (Array.isArray(executionTruth.tasks) ? executionTruth.tasks : [])
149
+ .filter((task) => task.execution_truth === "provider_wiring_required")
150
+ .map((task) => task.task_ref);
151
+ actions.push({
152
+ priority: "high",
153
+ action: "bind_provider_execution_targets",
154
+ reason: `provider-backed execution is still missing for ${providerRequiredTasks.join(", ") || "one or more capability lanes"}`,
155
+ refs: [
156
+ "/v1/capability-registry/execution-truth",
157
+ "/v1/capability-registry/commercialization-summary"
158
+ ]
159
+ });
160
+ }
161
+
162
+ if (settlementReadiness.ready !== true) {
163
+ actions.push({
164
+ priority: "high",
165
+ action: "close_native_settlement_inputs",
166
+ reason: `native settlement readiness is blocked by ${(settlementReadiness.blockers || []).join(", ") || "unresolved inputs"}`,
167
+ refs: [
168
+ "/v1/settlement/bsv-teranode/readiness",
169
+ "/v1/settlement/bsv-teranode"
170
+ ]
171
+ });
172
+ }
173
+
174
+ if (blockingIssues.some((issue) => issue.lane === "hosted_checkout_ingress")) {
175
+ actions.push({
176
+ priority: "high",
177
+ action: "configure_checkout_webhook_verification",
178
+ reason: "hosted checkout event ingress should be signed before xytara trusts payment-state transitions",
179
+ refs: [
180
+ "/v1/checkout/events/security",
181
+ "/v1/stripe-mpp"
182
+ ]
183
+ });
184
+ }
185
+
186
+ if (Number(treasurySummary.explicit_per_rail_configured_count || 0) < 1) {
187
+ actions.push({
188
+ priority: "medium",
189
+ action: "upgrade_from_fallback_to_per_rail_treasury_profiles",
190
+ reason: "explicit per-rail treasury profiles strengthen reconciliation and public claim integrity",
191
+ refs: [
192
+ "/v1/treasury-destinations",
193
+ "/v1/treasury-destinations/operator-pack"
194
+ ]
195
+ });
196
+ }
197
+
198
+ if (operatorIntelligence && operatorIntelligence.recommended_operator_motion) {
199
+ actions.push({
200
+ priority: operatorIntelligence.overall_operator_state && operatorIntelligence.overall_operator_state.top_priority
201
+ ? operatorIntelligence.overall_operator_state.top_priority
202
+ : "medium",
203
+ action: operatorIntelligence.recommended_operator_motion,
204
+ reason: "current operator intelligence indicates this as the next best runtime or adoption move",
205
+ refs: [
206
+ "/v1/operator-intelligence",
207
+ `/v1/economics/accounts/${encodeURIComponent(operatorIntelligence.account_id || "acct_demo")}/operator-dashboard`
208
+ ]
209
+ });
210
+ }
211
+
212
+ if (actions.length === 0) {
213
+ actions.push({
214
+ priority: "low",
215
+ action: "run_first_live_money_flow",
216
+ reason: "core launch blockers are cleared enough to run a first live end-to-end production transaction",
217
+ refs: [
218
+ "/v1/payment-ledger",
219
+ "/v1/activity-ledger",
220
+ "/v1/reconciliation-report"
221
+ ]
222
+ });
223
+ }
224
+
225
+ return actions;
226
+ }
227
+
228
+ function buildGoLiveOperatorPack(state, input, options) {
229
+ const payload = input && typeof input === "object" && !Array.isArray(input) ? input : {};
230
+ const accountId = String(payload.account_id || "acct_demo").trim() || "acct_demo";
231
+ const runtimeDurabilityBuilder = normalizeFunction(options && options.buildRuntimeDurabilitySummary, buildDefaultRuntimeDurability);
232
+ const settlementReadinessBuilder = normalizeFunction(options && options.buildNativeSettlementReadinessSummary, buildDefaultSettlementReadiness);
233
+
234
+ const executionTruth = buildCapabilityExecutionTruthBundle();
235
+ const commercialization = buildCapabilityCommercializationSummary();
236
+ const paymentStrategy = buildPaymentProtocolStrategyPack();
237
+ const treasurySummary = summarizeTreasuryDestinationsPack();
238
+ const checkoutSecurity = buildCheckoutEventSecuritySummary();
239
+ const runtimeDurability = runtimeDurabilityBuilder();
240
+ const settlementReadiness = settlementReadinessBuilder();
241
+ const operatorIntelligence = buildOperatorIntelligencePack(state, { account_id: accountId });
242
+ const executionTargets = loadExecutionTargetRegistry();
243
+ const blockingIssues = buildBlockingIssues(
244
+ executionTruth,
245
+ commercialization,
246
+ runtimeDurability,
247
+ settlementReadiness,
248
+ treasurySummary,
249
+ checkoutSecurity
250
+ );
251
+ const nextActions = buildNextActions(
252
+ blockingIssues,
253
+ operatorIntelligence,
254
+ executionTruth,
255
+ treasurySummary,
256
+ settlementReadiness
257
+ );
258
+
259
+ return {
260
+ ok: true,
261
+ product: packageJson.name,
262
+ category: "machine-commerce-go-live-operator-pack",
263
+ pack_version: "xytara-go-live-operator-pack-v1",
264
+ account_id: accountId,
265
+ go_live_state: blockingIssues.length === 0 ? "launch_ready_for_first_live_money_flow" : "launch_blocked_pending_operator_closeout",
266
+ launch_posture: {
267
+ public_capability_count: executionTruth.counts.task_count,
268
+ self_serve_live_now_count: commercialization.counts.self_serve_live_now,
269
+ operator_enabled_after_config_count: commercialization.counts.operator_enabled_after_config,
270
+ configured_execution_target_count: executionTargets.length,
271
+ configured_treasury_profile_count: treasurySummary.configured_profile_count,
272
+ explicit_per_rail_treasury_profile_count: treasurySummary.explicit_per_rail_configured_count,
273
+ runtime_recovery_posture: runtimeDurability.recovery_posture,
274
+ native_settlement_ready: settlementReadiness.ready === true,
275
+ checkout_ingress_security_ready: checkoutSecurity.ready === true
276
+ },
277
+ payment_posture: {
278
+ machine_primary: paymentStrategy.primary_machine_payment_front || null,
279
+ repeat_use_primary: paymentStrategy.primary_repeat_use_payment_front || null,
280
+ human_primary: paymentStrategy.human_and_enterprise_procurement_front || null,
281
+ external_front_count: Array.isArray(paymentStrategy.adapter_ready_external_fronts)
282
+ ? paymentStrategy.adapter_ready_external_fronts.length
283
+ : 0
284
+ },
285
+ blocking_issues: blockingIssues,
286
+ next_actions: nextActions,
287
+ launch_sequence: [
288
+ "configure_treasury_and_wallet_security",
289
+ "bind_provider_execution_targets_for_external_capability_lanes",
290
+ "close_native_settlement_readiness_inputs",
291
+ "confirm_persistent_runtime_storage_and_recovery",
292
+ "run_first_live_end_to_end_money_flow",
293
+ "watch_activity_payment_and_reconciliation_surfaces"
294
+ ],
295
+ linked_surfaces: {
296
+ capability_execution_truth_ref: "/v1/capability-registry/execution-truth",
297
+ commercialization_summary_ref: "/v1/capability-registry/commercialization-summary",
298
+ payment_strategy_ref: "/v1/payment-protocols/strategy",
299
+ payment_fronts_ref: "/v1/payment-fronts",
300
+ checkout_event_security_ref: "/v1/checkout/events/security",
301
+ treasury_ref: "/v1/treasury-destinations",
302
+ native_settlement_ref: "/v1/settlement/bsv-teranode/readiness",
303
+ runtime_durability_ref: "/v1/runtime/durability",
304
+ operator_intelligence_ref: "/v1/operator-intelligence",
305
+ payment_ledger_ref: "/v1/payment-ledger",
306
+ reconciliation_ref: "/v1/reconciliation-report"
307
+ },
308
+ supporting_summaries: {
309
+ capability_counts: executionTruth.counts,
310
+ commercialization_counts: commercialization.counts,
311
+ treasury_summary: treasurySummary,
312
+ checkout_event_security: checkoutSecurity,
313
+ runtime_durability: runtimeDurability,
314
+ native_settlement: settlementReadiness,
315
+ operator_intelligence: summarizeOperatorIntelligencePack(state, { account_id: accountId })
316
+ }
317
+ };
318
+ }
319
+
320
+ function summarizeGoLiveOperatorPack(state, input, options) {
321
+ const pack = buildGoLiveOperatorPack(state, input, options);
322
+ return {
323
+ ok: true,
324
+ product: pack.product,
325
+ category: pack.category,
326
+ pack_version: pack.pack_version,
327
+ account_id: pack.account_id,
328
+ go_live_state: pack.go_live_state,
329
+ blocking_issue_count: pack.blocking_issues.length,
330
+ top_action: pack.next_actions[0] || null,
331
+ launch_posture: pack.launch_posture,
332
+ linked_surfaces: pack.linked_surfaces
333
+ };
334
+ }
335
+
336
+ module.exports = {
337
+ buildGoLiveOperatorPack,
338
+ summarizeGoLiveOperatorPack
339
+ };
@@ -34,7 +34,39 @@ function readJsonBody(req) {
34
34
  });
35
35
  }
36
36
 
37
+ function readJsonBodyWithRaw(req) {
38
+ return new Promise((resolve, reject) => {
39
+ let body = "";
40
+ req.on("data", (chunk) => {
41
+ body += chunk;
42
+ if (body.length > 1024 * 1024) {
43
+ reject(new Error("request body too large"));
44
+ req.destroy();
45
+ }
46
+ });
47
+ req.on("end", () => {
48
+ if (!body) {
49
+ resolve({
50
+ body: {},
51
+ raw_body: ""
52
+ });
53
+ return;
54
+ }
55
+ try {
56
+ resolve({
57
+ body: JSON.parse(body),
58
+ raw_body: body
59
+ });
60
+ } catch (error) {
61
+ reject(new Error("body must be valid JSON"));
62
+ }
63
+ });
64
+ req.on("error", reject);
65
+ });
66
+ }
67
+
37
68
  module.exports = {
38
69
  readJsonBody,
70
+ readJsonBodyWithRaw,
39
71
  sendJson
40
72
  };
@@ -7,6 +7,8 @@ const { summarizeMcpLanePack } = require("./mcp_lane_contract");
7
7
  const { summarizeA2ALanePack } = require("./a2a_lane_contract");
8
8
  const { summarizeA2CLanePack } = require("./a2c_lane_contract");
9
9
  const { summarizeX402LanePack } = require("./x402_lane_contract");
10
+ const { summarizeL402LanePack } = require("./l402_lane_contract");
11
+ const { summarizeBoltLanePack } = require("./bolt_lane_contract");
10
12
  const { summarizeSettlementLanePack } = require("./settlement_lane_contract");
11
13
  const { summarizeStablecoinLanePack } = require("./stablecoin_lane_contract");
12
14
  const { summarizeMajorRailsLanePack } = require("./major_rails_lane_contract");
@@ -41,6 +43,8 @@ function buildIntegrationMatrixPack(integrations = []) {
41
43
  a2a: summarizeA2ALanePack(),
42
44
  a2c: summarizeA2CLanePack(),
43
45
  x402: summarizeX402LanePack(),
46
+ l402: summarizeL402LanePack(),
47
+ bolt: summarizeBoltLanePack(),
44
48
  settlement: summarizeSettlementLanePack(),
45
49
  stablecoins: summarizeStablecoinLanePack(),
46
50
  major_rails: summarizeMajorRailsLanePack(),
@@ -60,6 +64,8 @@ function buildIntegrationMatrixPack(integrations = []) {
60
64
  framework_lane_ref: "/v1/frameworks",
61
65
  protocol_lane_ref: "/v1/protocols",
62
66
  mcp_lane_ref: "/v1/mcp",
67
+ l402_lane_ref: "/v1/l402",
68
+ bolt_lane_ref: "/v1/bolt",
63
69
  settlement_lane_ref: "/v1/settlement",
64
70
  event_system_lane_ref: "/v1/event-systems",
65
71
  telemetry_lane_ref: "/v1/telemetry",
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ function buildL402LanePack() {
4
+ return {
5
+ product: "xytara",
6
+ category: "machine-commerce-l402-lane-pack",
7
+ pack_version: "xytara-l402-lane-pack-v1",
8
+ lane_state: "adapter_front_contract_present",
9
+ posture: "lightning_plus_http_402_permissionless_payment_front",
10
+ settlement_family: "bitcoin_lightning",
11
+ auth_family: "macaroon_scoped_access",
12
+ role_in_xytara: "optional_permissionless_machine_payment_front",
13
+ supported_surfaces: {
14
+ security_ref: "/v1/l402/security",
15
+ commands_execute_ref: "/l402/commands/execute",
16
+ mcp_invoke_ref: "/l402/mcp/tools/invoke",
17
+ payment_protocols_ref: "/v1/payment-protocols",
18
+ payment_protocol_strategy_ref: "/v1/payment-protocols/strategy",
19
+ capability_commercialization_ref: "/v1/capability-registry/commercialization-summary",
20
+ execution_truth_ref: "/v1/capability-registry/execution-truth"
21
+ },
22
+ integration_rules: [
23
+ "use L402 as an adapter payment front into the same execution, proof, and governance core",
24
+ "do not fork runtime semantics by payment front",
25
+ "bind Lightning-native verification and access issuance through an adapter or gateway layer"
26
+ ],
27
+ recommended_uses: [
28
+ "permission-sensitive agents",
29
+ "micropayment-heavy workloads",
30
+ "operators who want lower dependency on card or stablecoin facilitators"
31
+ ],
32
+ non_claims: [
33
+ "not claimed as the launch-default machine payment lane",
34
+ "not claimed as a built-in first-party Lightning settlement runtime today"
35
+ ]
36
+ };
37
+ }
38
+
39
+ function summarizeL402LanePack() {
40
+ const pack = buildL402LanePack();
41
+ return {
42
+ product: pack.product,
43
+ category: pack.category,
44
+ summary_version: "xytara-l402-lane-pack-summary-v1",
45
+ lane_state: pack.lane_state,
46
+ posture: pack.posture,
47
+ supported_surface_count: Object.keys(pack.supported_surfaces).length,
48
+ linked_surfaces: pack.supported_surfaces
49
+ };
50
+ }
51
+
52
+ module.exports = {
53
+ buildL402LanePack,
54
+ summarizeL402LanePack
55
+ };
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+
5
+ function normalizeString(value) {
6
+ return typeof value === "string" ? value.trim() : "";
7
+ }
8
+
9
+ function safeEqualHex(left, right) {
10
+ const a = Buffer.from(String(left || ""), "hex");
11
+ const b = Buffer.from(String(right || ""), "hex");
12
+ if (a.length === 0 || b.length === 0 || a.length !== b.length) return false;
13
+ return crypto.timingSafeEqual(a, b);
14
+ }
15
+
16
+ function decodeBase64UrlJson(value) {
17
+ try {
18
+ return JSON.parse(Buffer.from(String(value || ""), "base64url").toString("utf8"));
19
+ } catch (_) {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ function loadL402FrontConfig() {
25
+ const sharedSecret = normalizeString(process.env.XYTARA_L402_SHARED_SECRET);
26
+ return {
27
+ enabled: String(process.env.XYTARA_L402_ENABLED || (sharedSecret ? "true" : "false")).trim().toLowerCase() !== "false",
28
+ shared_secret: sharedSecret || null,
29
+ max_age_seconds: Math.max(30, Number(process.env.XYTARA_L402_MAX_AGE_SECONDS || 300) || 300),
30
+ required_headers: ["authorization"],
31
+ auth_scheme: "L402",
32
+ token_shape: "base64url_json_with_macaroon_and_hmac_signed_payload"
33
+ };
34
+ }
35
+
36
+ function parseL402Authorization(headers) {
37
+ const authorization = headers && typeof headers.authorization === "string"
38
+ ? headers.authorization.trim()
39
+ : "";
40
+ const match = authorization.match(/^L402\s+(.+)$/i);
41
+ if (!match) return null;
42
+ const decoded = decodeBase64UrlJson(match[1]);
43
+ if (!decoded || typeof decoded !== "object") return null;
44
+ return {
45
+ wallet_id: decoded.wallet_id || decoded.gateway_id || "l402_gateway",
46
+ encoded: decoded.payload || null,
47
+ signature: decoded.signature || null,
48
+ macaroon: decoded.macaroon || null,
49
+ invoice_id: decoded.invoice_id || null,
50
+ preimage_hash: decoded.preimage_hash || null,
51
+ scheme: "l402"
52
+ };
53
+ }
54
+
55
+ function verifyL402PaymentPayload(paymentPayload, quote, walletId, maxAgeSeconds) {
56
+ if (!paymentPayload || typeof paymentPayload !== "object") {
57
+ return { ok: false, reason: "l402_payload_invalid" };
58
+ }
59
+ if (!paymentPayload.quote_id || paymentPayload.quote_id !== quote.quote_id) {
60
+ return { ok: false, reason: "l402_quote_mismatch" };
61
+ }
62
+ if (paymentPayload.amount_minor !== quote.amount_minor) {
63
+ return { ok: false, reason: "l402_amount_mismatch" };
64
+ }
65
+ if (paymentPayload.currency !== quote.currency) {
66
+ return { ok: false, reason: "l402_currency_mismatch" };
67
+ }
68
+ if (paymentPayload.account_id && paymentPayload.account_id !== quote.account_id) {
69
+ return { ok: false, reason: "l402_account_mismatch" };
70
+ }
71
+ if (walletId && paymentPayload.wallet_id && paymentPayload.wallet_id !== walletId) {
72
+ return { ok: false, reason: "l402_wallet_mismatch" };
73
+ }
74
+ if (!paymentPayload.nonce || typeof paymentPayload.nonce !== "string") {
75
+ return { ok: false, reason: "l402_nonce_missing" };
76
+ }
77
+ if (!paymentPayload.issued_at_iso || Number.isNaN(Date.parse(paymentPayload.issued_at_iso))) {
78
+ return { ok: false, reason: "l402_issued_at_invalid" };
79
+ }
80
+ const ageSeconds = Math.max(0, Math.floor((Date.now() - Date.parse(paymentPayload.issued_at_iso)) / 1000));
81
+ if (ageSeconds > maxAgeSeconds) {
82
+ return { ok: false, reason: "l402_token_expired" };
83
+ }
84
+ if (!normalizeString(paymentPayload.macaroon_id || paymentPayload.macaroon)) {
85
+ return { ok: false, reason: "l402_macaroon_missing" };
86
+ }
87
+ if (!normalizeString(paymentPayload.invoice_id || paymentPayload.invoice_reference)) {
88
+ return { ok: false, reason: "l402_invoice_reference_missing" };
89
+ }
90
+ return { ok: true, age_seconds: ageSeconds };
91
+ }
92
+
93
+ function verifyL402AccessToken(headers, quote) {
94
+ const config = loadL402FrontConfig();
95
+ if (!config.enabled) {
96
+ return {
97
+ ok: false,
98
+ status: 503,
99
+ reason: "l402_not_enabled"
100
+ };
101
+ }
102
+ if (!config.shared_secret) {
103
+ return {
104
+ ok: false,
105
+ status: 503,
106
+ reason: "l402_shared_secret_missing"
107
+ };
108
+ }
109
+ const payment = parseL402Authorization(headers || {});
110
+ if (!payment || !payment.encoded || !payment.signature || !payment.macaroon) {
111
+ return {
112
+ ok: false,
113
+ status: 402,
114
+ reason: "l402_headers_incomplete"
115
+ };
116
+ }
117
+ const expectedSignature = crypto.createHmac("sha256", config.shared_secret).update(payment.encoded).digest("hex");
118
+ if (!safeEqualHex(expectedSignature, payment.signature)) {
119
+ return {
120
+ ok: false,
121
+ status: 403,
122
+ reason: "l402_signature_invalid"
123
+ };
124
+ }
125
+ const payload = decodeBase64UrlJson(payment.encoded);
126
+ const validation = verifyL402PaymentPayload(payload, quote, payment.wallet_id, config.max_age_seconds);
127
+ if (!validation.ok) {
128
+ return {
129
+ ok: false,
130
+ status: 403,
131
+ reason: validation.reason
132
+ };
133
+ }
134
+ return {
135
+ ok: true,
136
+ verification_mode: "l402_gateway_signed",
137
+ wallet_id: payment.wallet_id,
138
+ payment_payload: payload,
139
+ age_seconds: validation.age_seconds,
140
+ l402: {
141
+ macaroon: payment.macaroon,
142
+ invoice_id: payment.invoice_id || payload.invoice_id || null,
143
+ preimage_hash: payment.preimage_hash || payload.preimage_hash || null
144
+ }
145
+ };
146
+ }
147
+
148
+ function buildL402SecuritySummary() {
149
+ const config = loadL402FrontConfig();
150
+ const blockers = [];
151
+ if (config.enabled && !config.shared_secret) blockers.push("l402_shared_secret_missing");
152
+ return {
153
+ ok: true,
154
+ category: "machine-commerce-l402-security-summary",
155
+ summary_version: "xytara-l402-security-summary-v1",
156
+ enabled: config.enabled,
157
+ shared_secret_configured: Boolean(config.shared_secret),
158
+ max_age_seconds: config.max_age_seconds,
159
+ auth_scheme: config.auth_scheme,
160
+ token_shape: config.token_shape,
161
+ ready: config.enabled && blockers.length === 0,
162
+ blockers,
163
+ linked_surfaces: {
164
+ l402_lane_ref: "/v1/l402",
165
+ commands_execute_ref: "/l402/commands/execute",
166
+ mcp_invoke_ref: "/l402/mcp/tools/invoke"
167
+ }
168
+ };
169
+ }
170
+
171
+ function buildL402ChallengePayload(quote) {
172
+ return {
173
+ ok: false,
174
+ payment_required: {
175
+ scheme: "l402",
176
+ quote,
177
+ operator_requirements: {
178
+ auth_scheme: "L402",
179
+ token_shape: "base64url_json_with_macaroon_and_hmac_signed_payload",
180
+ security_ref: "/v1/l402/security"
181
+ }
182
+ }
183
+ };
184
+ }
185
+
186
+ module.exports = {
187
+ buildL402ChallengePayload,
188
+ buildL402SecuritySummary,
189
+ loadL402FrontConfig,
190
+ parseL402Authorization,
191
+ verifyL402AccessToken
192
+ };