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.
- package/.env.example +8 -1
- package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
- package/RELEASE_NOTES.md +6 -4
- package/lib/a2a_rpc.js +280 -280
- package/lib/account_auth.js +585 -585
- package/lib/agent_identity.js +162 -162
- package/lib/bolt_adapter.js +187 -187
- package/lib/bolt_payment_front.js +224 -224
- package/lib/checkout_processor_adapter.js +217 -217
- package/lib/command_flow.js +722 -722
- package/lib/commerce_artifacts.js +591 -591
- package/lib/commerce_bridge.js +274 -274
- package/lib/commerce_cases.js +399 -399
- package/lib/commerce_client.js +7694 -7694
- package/lib/commerce_quotes.js +29 -0
- package/lib/commerce_runtime.js +322 -322
- package/lib/execution_dedupe.js +86 -86
- package/lib/external_credit_bridge.js +142 -142
- package/lib/http_transport.js +81 -81
- package/lib/l402_adapter.js +241 -241
- package/lib/l402_payment_front.js +265 -265
- package/lib/mpp_adapter.js +237 -237
- package/lib/payment_config.js +253 -253
- package/lib/payment_verification.js +357 -357
- package/lib/provider_payout_execution.js +155 -155
- package/lib/provider_registry.js +842 -842
- package/lib/rate_limiter.js +81 -81
- package/lib/runtime_state_store.js +491 -369
- package/lib/settlement_bsv_live.js +600 -600
- package/lib/settlement_bsv_teranode.js +117 -117
- package/lib/stripe_mpp_payment_front.js +258 -258
- package/lib/x402_facilitator.js +191 -191
- package/package.json +5 -3
- package/scripts/state_backup_offsite.js +196 -0
- package/scripts/verify_a2a_rpc.js +105 -105
- package/scripts/verify_agent_identity.js +153 -153
- package/scripts/verify_all.js +1 -1
- package/scripts/verify_bolt_adapter.js +128 -128
- package/scripts/verify_cdp_facilitator.js +110 -110
- package/scripts/verify_checkout_processor.js +109 -109
- package/scripts/verify_commerce_bridge.js +177 -177
- package/scripts/verify_execution_dedupe.js +189 -189
- package/scripts/verify_execution_surfacing.js +76 -76
- package/scripts/verify_final_activation.js +191 -191
- package/scripts/verify_l402_adapter.js +86 -86
- package/scripts/verify_mpp_adapter.js +128 -128
- package/scripts/verify_production_readiness.js +268 -268
- package/scripts/verify_rate_limiter.js +52 -52
- package/scripts/verify_refund_credit.js +106 -106
- package/scripts/verify_release_candidate.js +81 -81
- package/scripts/verify_runtime_state_guard.js +117 -0
- package/scripts/verify_service.js +15421 -15421
- package/scripts/verify_verify_execute_settle.js +208 -208
- package/scripts/verify_x402_facilitator.js +286 -286
- package/server.js +11976 -11974
|
@@ -1,268 +1,268 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const assert = require("assert");
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
const { execFileSync } = require("child_process");
|
|
7
|
-
|
|
8
|
-
function firstEnv(...names) {
|
|
9
|
-
for (const name of names) {
|
|
10
|
-
const value = process.env[name];
|
|
11
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
12
|
-
return value.trim();
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return "";
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function ensureReleaseCandidate() {
|
|
19
|
-
const releaseOutput = execFileSync(process.execPath, [
|
|
20
|
-
path.resolve(__dirname, "..", "bin", "xytara-release.js"),
|
|
21
|
-
"--candidate",
|
|
22
|
-
"--summary"
|
|
23
|
-
], {
|
|
24
|
-
cwd: path.resolve(__dirname, ".."),
|
|
25
|
-
encoding: "utf8"
|
|
26
|
-
});
|
|
27
|
-
const releaseCandidate = JSON.parse(releaseOutput);
|
|
28
|
-
assert.strictEqual(releaseCandidate.product, "xytara", "release candidate product mismatch");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function ensurePersistenceConfig() {
|
|
32
|
-
const stateFile = process.env.XYTARA_STATE_FILE;
|
|
33
|
-
const supabaseUrl = process.env.XYTARA_SUPABASE_URL;
|
|
34
|
-
const supabaseServiceRoleKey = process.env.XYTARA_SUPABASE_SERVICE_ROLE_KEY;
|
|
35
|
-
|
|
36
|
-
const fileConfigured = typeof stateFile === "string" && stateFile.trim().length > 0;
|
|
37
|
-
const supabaseConfigured =
|
|
38
|
-
typeof supabaseUrl === "string" &&
|
|
39
|
-
supabaseUrl.trim().length > 0 &&
|
|
40
|
-
typeof supabaseServiceRoleKey === "string" &&
|
|
41
|
-
supabaseServiceRoleKey.trim().length > 0;
|
|
42
|
-
|
|
43
|
-
assert.strictEqual(
|
|
44
|
-
fileConfigured || supabaseConfigured,
|
|
45
|
-
true,
|
|
46
|
-
"configure XYTARA_STATE_FILE on persistent disk or XYTARA_SUPABASE_URL + XYTARA_SUPABASE_SERVICE_ROLE_KEY before treating xytara as production-ready"
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
if (fileConfigured) {
|
|
50
|
-
const resolvedPath = path.resolve(stateFile);
|
|
51
|
-
const parentDirectory = path.dirname(resolvedPath);
|
|
52
|
-
assert.strictEqual(
|
|
53
|
-
fs.existsSync(parentDirectory),
|
|
54
|
-
true,
|
|
55
|
-
`XYTARA_STATE_FILE parent directory does not exist: ${parentDirectory}`
|
|
56
|
-
);
|
|
57
|
-
fs.accessSync(parentDirectory, fs.constants.W_OK);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function ensureProductionSecurityConfig() {
|
|
62
|
-
const verificationMode = String(process.env.XYTARA_PAYMENT_VERIFICATION_MODE || "").trim() || "presence";
|
|
63
|
-
assert.notStrictEqual(
|
|
64
|
-
verificationMode,
|
|
65
|
-
"presence",
|
|
66
|
-
"set XYTARA_PAYMENT_VERIFICATION_MODE to local_signed or stronger before treating xytara as production-ready"
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
if (verificationMode === "local_signed") {
|
|
70
|
-
const walletRegistryJson = String(process.env.XYTARA_WALLET_REGISTRY_JSON || "").trim();
|
|
71
|
-
assert.strictEqual(walletRegistryJson.length > 0, true, "configure XYTARA_WALLET_REGISTRY_JSON before using local_signed payment verification");
|
|
72
|
-
const parsed = JSON.parse(walletRegistryJson);
|
|
73
|
-
assert.strictEqual(Array.isArray(parsed), true, "XYTARA_WALLET_REGISTRY_JSON must be a JSON array");
|
|
74
|
-
assert.strictEqual(parsed.length > 0, true, "register at least one enabled wallet in XYTARA_WALLET_REGISTRY_JSON before treating xytara as production-ready");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
assert.strictEqual(
|
|
78
|
-
String(process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN || "").trim().length > 0,
|
|
79
|
-
true,
|
|
80
|
-
"configure XYTARA_CREDIT_BRIDGE_BEARER_TOKEN before treating xytara as production-ready"
|
|
81
|
-
);
|
|
82
|
-
assert.strictEqual(
|
|
83
|
-
String(process.env.XYTARA_ACCOUNT_AUTH_BEARER_TOKEN || "").trim().length > 0,
|
|
84
|
-
true,
|
|
85
|
-
"configure XYTARA_ACCOUNT_AUTH_BEARER_TOKEN before treating xytara as production-ready"
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function ensureTreasuryConfig() {
|
|
90
|
-
const treasuryPath = String(process.env.XYTARA_TREASURY_DESTINATIONS_PATH || "").trim();
|
|
91
|
-
assert.strictEqual(
|
|
92
|
-
treasuryPath.length > 0,
|
|
93
|
-
true,
|
|
94
|
-
"configure XYTARA_TREASURY_DESTINATIONS_PATH before treating xytara as production-ready"
|
|
95
|
-
);
|
|
96
|
-
const resolvedPath = path.resolve(treasuryPath);
|
|
97
|
-
assert.strictEqual(fs.existsSync(resolvedPath), true, `treasury destinations file does not exist: ${resolvedPath}`);
|
|
98
|
-
const parsed = JSON.parse(fs.readFileSync(resolvedPath, "utf8"));
|
|
99
|
-
const configuredRails = Object.values(parsed || {}).filter((entry) =>
|
|
100
|
-
entry
|
|
101
|
-
&& typeof entry === "object"
|
|
102
|
-
&& typeof entry.external_ref === "string"
|
|
103
|
-
&& entry.external_ref.trim().length > 0
|
|
104
|
-
);
|
|
105
|
-
assert.strictEqual(
|
|
106
|
-
configuredRails.length > 0,
|
|
107
|
-
true,
|
|
108
|
-
"configure at least one treasury destination external_ref before treating xytara as production-ready"
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function ensureCallbackDeliveryConfig() {
|
|
113
|
-
const callbackDeliveryEnabled = String(process.env.XYTARA_CALLBACK_DELIVERY_ENABLED || "true").trim().toLowerCase() !== "false";
|
|
114
|
-
if (!callbackDeliveryEnabled) return;
|
|
115
|
-
assert.strictEqual(
|
|
116
|
-
String(process.env.XYTARA_CALLBACK_SIGNING_SECRET || "").trim().length > 0,
|
|
117
|
-
true,
|
|
118
|
-
"configure XYTARA_CALLBACK_SIGNING_SECRET before treating xytara as production-ready"
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function ensureHostedCheckoutIngressSecurity() {
|
|
123
|
-
const verificationRequired = String(
|
|
124
|
-
process.env.XYTARA_CHECKOUT_WEBHOOK_VERIFICATION_REQUIRED
|
|
125
|
-
|| process.env.XYTARA_HOSTED_CHECKOUT_ENABLED
|
|
126
|
-
|| "true"
|
|
127
|
-
).trim().toLowerCase();
|
|
128
|
-
if (["false", "0", "no", "off"].includes(verificationRequired)) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
assert.strictEqual(
|
|
132
|
-
String(process.env.XYTARA_CHECKOUT_WEBHOOK_SECRET || process.env.XYTARA_HOSTED_CHECKOUT_WEBHOOK_SECRET || "").trim().length > 0,
|
|
133
|
-
true,
|
|
134
|
-
"configure XYTARA_CHECKOUT_WEBHOOK_SECRET before treating hosted-checkout ingress as production-ready"
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function ensureL402Config() {
|
|
139
|
-
const l402Enabled = String(process.env.XYTARA_L402_ENABLED || "false").trim().toLowerCase() === "true";
|
|
140
|
-
if (!l402Enabled) return;
|
|
141
|
-
assert.strictEqual(
|
|
142
|
-
String(process.env.XYTARA_L402_SHARED_SECRET || "").trim().length > 0,
|
|
143
|
-
true,
|
|
144
|
-
"configure XYTARA_L402_SHARED_SECRET before treating the L402 ingress as production-ready"
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function ensureBoltConfig() {
|
|
149
|
-
const boltEnabled = String(process.env.XYTARA_BOLT_ENABLED || "false").trim().toLowerCase() === "true";
|
|
150
|
-
if (!boltEnabled) return;
|
|
151
|
-
assert.strictEqual(
|
|
152
|
-
String(process.env.XYTARA_BOLT_SHARED_SECRET || "").trim().length > 0,
|
|
153
|
-
true,
|
|
154
|
-
"configure XYTARA_BOLT_SHARED_SECRET before treating the BOLT ingress as production-ready"
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function ensureMppConfig() {
|
|
159
|
-
const mppEnabled = String(process.env.XYTARA_MPP_ENABLED || "false").trim().toLowerCase() === "true";
|
|
160
|
-
if (!mppEnabled) return;
|
|
161
|
-
assert.strictEqual(
|
|
162
|
-
String(process.env.XYTARA_MPP_SHARED_SECRET || "").trim().length > 0,
|
|
163
|
-
true,
|
|
164
|
-
"configure XYTARA_MPP_SHARED_SECRET before treating the MPP ingress as production-ready"
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function ensureRateLimitConfig() {
|
|
169
|
-
const rateLimitEnabled = String(process.env.XYTARA_RATE_LIMIT_ENABLED || "true").trim().toLowerCase() !== "false";
|
|
170
|
-
assert.strictEqual(
|
|
171
|
-
rateLimitEnabled,
|
|
172
|
-
true,
|
|
173
|
-
"keep XYTARA_RATE_LIMIT_ENABLED=true before treating xytara as production-ready unless you have an equivalent upstream control"
|
|
174
|
-
);
|
|
175
|
-
const windowMs = Number(process.env.XYTARA_RATE_LIMIT_WINDOW_MS || 0);
|
|
176
|
-
const maxRequests = Number(process.env.XYTARA_RATE_LIMIT_MAX_REQUESTS || 0);
|
|
177
|
-
assert.strictEqual(Number.isFinite(windowMs) && windowMs > 0, true, "configure XYTARA_RATE_LIMIT_WINDOW_MS to a positive value");
|
|
178
|
-
assert.strictEqual(Number.isFinite(maxRequests) && maxRequests > 0, true, "configure XYTARA_RATE_LIMIT_MAX_REQUESTS to a positive value");
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function ensureExecutionTargetConfig() {
|
|
182
|
-
const raw = String(process.env.XYTARA_EXECUTION_TARGETS_JSON || "").trim();
|
|
183
|
-
const requireTargets = String(process.env.XYTARA_EXTERNAL_EXECUTION_REQUIRED || "false").trim().toLowerCase() === "true";
|
|
184
|
-
if (!raw) {
|
|
185
|
-
assert.strictEqual(
|
|
186
|
-
requireTargets,
|
|
187
|
-
false,
|
|
188
|
-
"configure XYTARA_EXECUTION_TARGETS_JSON before treating xytara as production-ready when XYTARA_EXTERNAL_EXECUTION_REQUIRED=true"
|
|
189
|
-
);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const parsed = JSON.parse(raw);
|
|
194
|
-
assert.strictEqual(Array.isArray(parsed), true, "XYTARA_EXECUTION_TARGETS_JSON must be a JSON array");
|
|
195
|
-
assert.strictEqual(parsed.length > 0, true, "configure at least one execution target in XYTARA_EXECUTION_TARGETS_JSON");
|
|
196
|
-
parsed.forEach((entry, index) => {
|
|
197
|
-
assert.strictEqual(
|
|
198
|
-
entry && typeof entry === "object" && !Array.isArray(entry),
|
|
199
|
-
true,
|
|
200
|
-
`execution target ${index + 1} must be an object`
|
|
201
|
-
);
|
|
202
|
-
assert.strictEqual(
|
|
203
|
-
typeof entry.endpoint_url === "string" && entry.endpoint_url.trim().length > 0,
|
|
204
|
-
true,
|
|
205
|
-
`execution target ${index + 1} must include endpoint_url`
|
|
206
|
-
);
|
|
207
|
-
assert.strictEqual(
|
|
208
|
-
Boolean(
|
|
209
|
-
(typeof entry.task_ref === "string" && entry.task_ref.trim().length > 0)
|
|
210
|
-
|| (typeof entry.tool_name === "string" && entry.tool_name.trim().length > 0)
|
|
211
|
-
|| (typeof entry.integration_id === "string" && entry.integration_id.trim().length > 0)
|
|
212
|
-
),
|
|
213
|
-
true,
|
|
214
|
-
`execution target ${index + 1} must match at least one of task_ref, tool_name, or integration_id`
|
|
215
|
-
);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function ensureNativeSettlementConfig() {
|
|
220
|
-
const runtimeMode = String(
|
|
221
|
-
firstEnv("XYTARA_BSV_SETTLEMENT_RUNTIME_MODE", "XYTARA_BSV_TERANODE_SETTLEMENT_RUNTIME_MODE") || "mock"
|
|
222
|
-
).trim().toLowerCase() || "mock";
|
|
223
|
-
assert.notStrictEqual(
|
|
224
|
-
runtimeMode,
|
|
225
|
-
"mock",
|
|
226
|
-
"configure XYTARA_BSV_SETTLEMENT_RUNTIME_MODE or XYTARA_BSV_TERANODE_SETTLEMENT_RUNTIME_MODE to observe, arc, or another live runtime mode before treating xytara as production-ready"
|
|
227
|
-
);
|
|
228
|
-
assert.strictEqual(
|
|
229
|
-
firstEnv(
|
|
230
|
-
"XYTARA_BSV_MERCHANT_ADDRESS",
|
|
231
|
-
"XYTARA_BSV_TERANODE_MERCHANT_ADDRESS",
|
|
232
|
-
"XYTARA_BSV_MERCHANT_PAYMAIL",
|
|
233
|
-
"XYTARA_BSV_TERANODE_MERCHANT_PAYMAIL"
|
|
234
|
-
).length > 0,
|
|
235
|
-
true,
|
|
236
|
-
"configure XYTARA_BSV_MERCHANT_ADDRESS / XYTARA_BSV_MERCHANT_PAYMAIL or the legacy XYTARA_BSV_TERANODE_* merchant vars before treating xytara as production-ready"
|
|
237
|
-
);
|
|
238
|
-
if (runtimeMode === "arc") {
|
|
239
|
-
assert.strictEqual(
|
|
240
|
-
firstEnv("XYTARA_BSV_ARC_RPC_URL", "XYTARA_BSV_ARC_URL", "XYTARA_BSV_TERANODE_RPC_URL").length > 0,
|
|
241
|
-
true,
|
|
242
|
-
"configure XYTARA_BSV_ARC_RPC_URL, XYTARA_BSV_ARC_URL, or XYTARA_BSV_TERANODE_RPC_URL before treating arc settlement mode as production-ready"
|
|
243
|
-
);
|
|
244
|
-
assert.strictEqual(
|
|
245
|
-
firstEnv("XYTARA_BSV_ARC_RPC_API_KEY", "XYTARA_BSV_ARC_API_KEY", "XYTARA_BSV_TERANODE_RPC_API_KEY").length > 0,
|
|
246
|
-
true,
|
|
247
|
-
"configure XYTARA_BSV_ARC_RPC_API_KEY, XYTARA_BSV_ARC_API_KEY, or XYTARA_BSV_TERANODE_RPC_API_KEY before treating arc settlement mode as production-ready"
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function main() {
|
|
253
|
-
ensureReleaseCandidate();
|
|
254
|
-
ensurePersistenceConfig();
|
|
255
|
-
ensureProductionSecurityConfig();
|
|
256
|
-
ensureTreasuryConfig();
|
|
257
|
-
ensureCallbackDeliveryConfig();
|
|
258
|
-
ensureHostedCheckoutIngressSecurity();
|
|
259
|
-
ensureL402Config();
|
|
260
|
-
ensureBoltConfig();
|
|
261
|
-
ensureMppConfig();
|
|
262
|
-
ensureRateLimitConfig();
|
|
263
|
-
ensureExecutionTargetConfig();
|
|
264
|
-
ensureNativeSettlementConfig();
|
|
265
|
-
console.log("xytara verify:production-readiness passed");
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
main();
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const assert = require("assert");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { execFileSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
function firstEnv(...names) {
|
|
9
|
+
for (const name of names) {
|
|
10
|
+
const value = process.env[name];
|
|
11
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
12
|
+
return value.trim();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ensureReleaseCandidate() {
|
|
19
|
+
const releaseOutput = execFileSync(process.execPath, [
|
|
20
|
+
path.resolve(__dirname, "..", "bin", "xytara-release.js"),
|
|
21
|
+
"--candidate",
|
|
22
|
+
"--summary"
|
|
23
|
+
], {
|
|
24
|
+
cwd: path.resolve(__dirname, ".."),
|
|
25
|
+
encoding: "utf8"
|
|
26
|
+
});
|
|
27
|
+
const releaseCandidate = JSON.parse(releaseOutput);
|
|
28
|
+
assert.strictEqual(releaseCandidate.product, "xytara", "release candidate product mismatch");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ensurePersistenceConfig() {
|
|
32
|
+
const stateFile = process.env.XYTARA_STATE_FILE;
|
|
33
|
+
const supabaseUrl = process.env.XYTARA_SUPABASE_URL;
|
|
34
|
+
const supabaseServiceRoleKey = process.env.XYTARA_SUPABASE_SERVICE_ROLE_KEY;
|
|
35
|
+
|
|
36
|
+
const fileConfigured = typeof stateFile === "string" && stateFile.trim().length > 0;
|
|
37
|
+
const supabaseConfigured =
|
|
38
|
+
typeof supabaseUrl === "string" &&
|
|
39
|
+
supabaseUrl.trim().length > 0 &&
|
|
40
|
+
typeof supabaseServiceRoleKey === "string" &&
|
|
41
|
+
supabaseServiceRoleKey.trim().length > 0;
|
|
42
|
+
|
|
43
|
+
assert.strictEqual(
|
|
44
|
+
fileConfigured || supabaseConfigured,
|
|
45
|
+
true,
|
|
46
|
+
"configure XYTARA_STATE_FILE on persistent disk or XYTARA_SUPABASE_URL + XYTARA_SUPABASE_SERVICE_ROLE_KEY before treating xytara as production-ready"
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (fileConfigured) {
|
|
50
|
+
const resolvedPath = path.resolve(stateFile);
|
|
51
|
+
const parentDirectory = path.dirname(resolvedPath);
|
|
52
|
+
assert.strictEqual(
|
|
53
|
+
fs.existsSync(parentDirectory),
|
|
54
|
+
true,
|
|
55
|
+
`XYTARA_STATE_FILE parent directory does not exist: ${parentDirectory}`
|
|
56
|
+
);
|
|
57
|
+
fs.accessSync(parentDirectory, fs.constants.W_OK);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function ensureProductionSecurityConfig() {
|
|
62
|
+
const verificationMode = String(process.env.XYTARA_PAYMENT_VERIFICATION_MODE || "").trim() || "presence";
|
|
63
|
+
assert.notStrictEqual(
|
|
64
|
+
verificationMode,
|
|
65
|
+
"presence",
|
|
66
|
+
"set XYTARA_PAYMENT_VERIFICATION_MODE to local_signed or stronger before treating xytara as production-ready"
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (verificationMode === "local_signed") {
|
|
70
|
+
const walletRegistryJson = String(process.env.XYTARA_WALLET_REGISTRY_JSON || "").trim();
|
|
71
|
+
assert.strictEqual(walletRegistryJson.length > 0, true, "configure XYTARA_WALLET_REGISTRY_JSON before using local_signed payment verification");
|
|
72
|
+
const parsed = JSON.parse(walletRegistryJson);
|
|
73
|
+
assert.strictEqual(Array.isArray(parsed), true, "XYTARA_WALLET_REGISTRY_JSON must be a JSON array");
|
|
74
|
+
assert.strictEqual(parsed.length > 0, true, "register at least one enabled wallet in XYTARA_WALLET_REGISTRY_JSON before treating xytara as production-ready");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
assert.strictEqual(
|
|
78
|
+
String(process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN || "").trim().length > 0,
|
|
79
|
+
true,
|
|
80
|
+
"configure XYTARA_CREDIT_BRIDGE_BEARER_TOKEN before treating xytara as production-ready"
|
|
81
|
+
);
|
|
82
|
+
assert.strictEqual(
|
|
83
|
+
String(process.env.XYTARA_ACCOUNT_AUTH_BEARER_TOKEN || "").trim().length > 0,
|
|
84
|
+
true,
|
|
85
|
+
"configure XYTARA_ACCOUNT_AUTH_BEARER_TOKEN before treating xytara as production-ready"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ensureTreasuryConfig() {
|
|
90
|
+
const treasuryPath = String(process.env.XYTARA_TREASURY_DESTINATIONS_PATH || "").trim();
|
|
91
|
+
assert.strictEqual(
|
|
92
|
+
treasuryPath.length > 0,
|
|
93
|
+
true,
|
|
94
|
+
"configure XYTARA_TREASURY_DESTINATIONS_PATH before treating xytara as production-ready"
|
|
95
|
+
);
|
|
96
|
+
const resolvedPath = path.resolve(treasuryPath);
|
|
97
|
+
assert.strictEqual(fs.existsSync(resolvedPath), true, `treasury destinations file does not exist: ${resolvedPath}`);
|
|
98
|
+
const parsed = JSON.parse(fs.readFileSync(resolvedPath, "utf8"));
|
|
99
|
+
const configuredRails = Object.values(parsed || {}).filter((entry) =>
|
|
100
|
+
entry
|
|
101
|
+
&& typeof entry === "object"
|
|
102
|
+
&& typeof entry.external_ref === "string"
|
|
103
|
+
&& entry.external_ref.trim().length > 0
|
|
104
|
+
);
|
|
105
|
+
assert.strictEqual(
|
|
106
|
+
configuredRails.length > 0,
|
|
107
|
+
true,
|
|
108
|
+
"configure at least one treasury destination external_ref before treating xytara as production-ready"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ensureCallbackDeliveryConfig() {
|
|
113
|
+
const callbackDeliveryEnabled = String(process.env.XYTARA_CALLBACK_DELIVERY_ENABLED || "true").trim().toLowerCase() !== "false";
|
|
114
|
+
if (!callbackDeliveryEnabled) return;
|
|
115
|
+
assert.strictEqual(
|
|
116
|
+
String(process.env.XYTARA_CALLBACK_SIGNING_SECRET || "").trim().length > 0,
|
|
117
|
+
true,
|
|
118
|
+
"configure XYTARA_CALLBACK_SIGNING_SECRET before treating xytara as production-ready"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function ensureHostedCheckoutIngressSecurity() {
|
|
123
|
+
const verificationRequired = String(
|
|
124
|
+
process.env.XYTARA_CHECKOUT_WEBHOOK_VERIFICATION_REQUIRED
|
|
125
|
+
|| process.env.XYTARA_HOSTED_CHECKOUT_ENABLED
|
|
126
|
+
|| "true"
|
|
127
|
+
).trim().toLowerCase();
|
|
128
|
+
if (["false", "0", "no", "off"].includes(verificationRequired)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
assert.strictEqual(
|
|
132
|
+
String(process.env.XYTARA_CHECKOUT_WEBHOOK_SECRET || process.env.XYTARA_HOSTED_CHECKOUT_WEBHOOK_SECRET || "").trim().length > 0,
|
|
133
|
+
true,
|
|
134
|
+
"configure XYTARA_CHECKOUT_WEBHOOK_SECRET before treating hosted-checkout ingress as production-ready"
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ensureL402Config() {
|
|
139
|
+
const l402Enabled = String(process.env.XYTARA_L402_ENABLED || "false").trim().toLowerCase() === "true";
|
|
140
|
+
if (!l402Enabled) return;
|
|
141
|
+
assert.strictEqual(
|
|
142
|
+
String(process.env.XYTARA_L402_SHARED_SECRET || "").trim().length > 0,
|
|
143
|
+
true,
|
|
144
|
+
"configure XYTARA_L402_SHARED_SECRET before treating the L402 ingress as production-ready"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ensureBoltConfig() {
|
|
149
|
+
const boltEnabled = String(process.env.XYTARA_BOLT_ENABLED || "false").trim().toLowerCase() === "true";
|
|
150
|
+
if (!boltEnabled) return;
|
|
151
|
+
assert.strictEqual(
|
|
152
|
+
String(process.env.XYTARA_BOLT_SHARED_SECRET || "").trim().length > 0,
|
|
153
|
+
true,
|
|
154
|
+
"configure XYTARA_BOLT_SHARED_SECRET before treating the BOLT ingress as production-ready"
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function ensureMppConfig() {
|
|
159
|
+
const mppEnabled = String(process.env.XYTARA_MPP_ENABLED || "false").trim().toLowerCase() === "true";
|
|
160
|
+
if (!mppEnabled) return;
|
|
161
|
+
assert.strictEqual(
|
|
162
|
+
String(process.env.XYTARA_MPP_SHARED_SECRET || "").trim().length > 0,
|
|
163
|
+
true,
|
|
164
|
+
"configure XYTARA_MPP_SHARED_SECRET before treating the MPP ingress as production-ready"
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ensureRateLimitConfig() {
|
|
169
|
+
const rateLimitEnabled = String(process.env.XYTARA_RATE_LIMIT_ENABLED || "true").trim().toLowerCase() !== "false";
|
|
170
|
+
assert.strictEqual(
|
|
171
|
+
rateLimitEnabled,
|
|
172
|
+
true,
|
|
173
|
+
"keep XYTARA_RATE_LIMIT_ENABLED=true before treating xytara as production-ready unless you have an equivalent upstream control"
|
|
174
|
+
);
|
|
175
|
+
const windowMs = Number(process.env.XYTARA_RATE_LIMIT_WINDOW_MS || 0);
|
|
176
|
+
const maxRequests = Number(process.env.XYTARA_RATE_LIMIT_MAX_REQUESTS || 0);
|
|
177
|
+
assert.strictEqual(Number.isFinite(windowMs) && windowMs > 0, true, "configure XYTARA_RATE_LIMIT_WINDOW_MS to a positive value");
|
|
178
|
+
assert.strictEqual(Number.isFinite(maxRequests) && maxRequests > 0, true, "configure XYTARA_RATE_LIMIT_MAX_REQUESTS to a positive value");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function ensureExecutionTargetConfig() {
|
|
182
|
+
const raw = String(process.env.XYTARA_EXECUTION_TARGETS_JSON || "").trim();
|
|
183
|
+
const requireTargets = String(process.env.XYTARA_EXTERNAL_EXECUTION_REQUIRED || "false").trim().toLowerCase() === "true";
|
|
184
|
+
if (!raw) {
|
|
185
|
+
assert.strictEqual(
|
|
186
|
+
requireTargets,
|
|
187
|
+
false,
|
|
188
|
+
"configure XYTARA_EXECUTION_TARGETS_JSON before treating xytara as production-ready when XYTARA_EXTERNAL_EXECUTION_REQUIRED=true"
|
|
189
|
+
);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const parsed = JSON.parse(raw);
|
|
194
|
+
assert.strictEqual(Array.isArray(parsed), true, "XYTARA_EXECUTION_TARGETS_JSON must be a JSON array");
|
|
195
|
+
assert.strictEqual(parsed.length > 0, true, "configure at least one execution target in XYTARA_EXECUTION_TARGETS_JSON");
|
|
196
|
+
parsed.forEach((entry, index) => {
|
|
197
|
+
assert.strictEqual(
|
|
198
|
+
entry && typeof entry === "object" && !Array.isArray(entry),
|
|
199
|
+
true,
|
|
200
|
+
`execution target ${index + 1} must be an object`
|
|
201
|
+
);
|
|
202
|
+
assert.strictEqual(
|
|
203
|
+
typeof entry.endpoint_url === "string" && entry.endpoint_url.trim().length > 0,
|
|
204
|
+
true,
|
|
205
|
+
`execution target ${index + 1} must include endpoint_url`
|
|
206
|
+
);
|
|
207
|
+
assert.strictEqual(
|
|
208
|
+
Boolean(
|
|
209
|
+
(typeof entry.task_ref === "string" && entry.task_ref.trim().length > 0)
|
|
210
|
+
|| (typeof entry.tool_name === "string" && entry.tool_name.trim().length > 0)
|
|
211
|
+
|| (typeof entry.integration_id === "string" && entry.integration_id.trim().length > 0)
|
|
212
|
+
),
|
|
213
|
+
true,
|
|
214
|
+
`execution target ${index + 1} must match at least one of task_ref, tool_name, or integration_id`
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function ensureNativeSettlementConfig() {
|
|
220
|
+
const runtimeMode = String(
|
|
221
|
+
firstEnv("XYTARA_BSV_SETTLEMENT_RUNTIME_MODE", "XYTARA_BSV_TERANODE_SETTLEMENT_RUNTIME_MODE") || "mock"
|
|
222
|
+
).trim().toLowerCase() || "mock";
|
|
223
|
+
assert.notStrictEqual(
|
|
224
|
+
runtimeMode,
|
|
225
|
+
"mock",
|
|
226
|
+
"configure XYTARA_BSV_SETTLEMENT_RUNTIME_MODE or XYTARA_BSV_TERANODE_SETTLEMENT_RUNTIME_MODE to observe, arc, or another live runtime mode before treating xytara as production-ready"
|
|
227
|
+
);
|
|
228
|
+
assert.strictEqual(
|
|
229
|
+
firstEnv(
|
|
230
|
+
"XYTARA_BSV_MERCHANT_ADDRESS",
|
|
231
|
+
"XYTARA_BSV_TERANODE_MERCHANT_ADDRESS",
|
|
232
|
+
"XYTARA_BSV_MERCHANT_PAYMAIL",
|
|
233
|
+
"XYTARA_BSV_TERANODE_MERCHANT_PAYMAIL"
|
|
234
|
+
).length > 0,
|
|
235
|
+
true,
|
|
236
|
+
"configure XYTARA_BSV_MERCHANT_ADDRESS / XYTARA_BSV_MERCHANT_PAYMAIL or the legacy XYTARA_BSV_TERANODE_* merchant vars before treating xytara as production-ready"
|
|
237
|
+
);
|
|
238
|
+
if (runtimeMode === "arc") {
|
|
239
|
+
assert.strictEqual(
|
|
240
|
+
firstEnv("XYTARA_BSV_ARC_RPC_URL", "XYTARA_BSV_ARC_URL", "XYTARA_BSV_TERANODE_RPC_URL").length > 0,
|
|
241
|
+
true,
|
|
242
|
+
"configure XYTARA_BSV_ARC_RPC_URL, XYTARA_BSV_ARC_URL, or XYTARA_BSV_TERANODE_RPC_URL before treating arc settlement mode as production-ready"
|
|
243
|
+
);
|
|
244
|
+
assert.strictEqual(
|
|
245
|
+
firstEnv("XYTARA_BSV_ARC_RPC_API_KEY", "XYTARA_BSV_ARC_API_KEY", "XYTARA_BSV_TERANODE_RPC_API_KEY").length > 0,
|
|
246
|
+
true,
|
|
247
|
+
"configure XYTARA_BSV_ARC_RPC_API_KEY, XYTARA_BSV_ARC_API_KEY, or XYTARA_BSV_TERANODE_RPC_API_KEY before treating arc settlement mode as production-ready"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function main() {
|
|
253
|
+
ensureReleaseCandidate();
|
|
254
|
+
ensurePersistenceConfig();
|
|
255
|
+
ensureProductionSecurityConfig();
|
|
256
|
+
ensureTreasuryConfig();
|
|
257
|
+
ensureCallbackDeliveryConfig();
|
|
258
|
+
ensureHostedCheckoutIngressSecurity();
|
|
259
|
+
ensureL402Config();
|
|
260
|
+
ensureBoltConfig();
|
|
261
|
+
ensureMppConfig();
|
|
262
|
+
ensureRateLimitConfig();
|
|
263
|
+
ensureExecutionTargetConfig();
|
|
264
|
+
ensureNativeSettlementConfig();
|
|
265
|
+
console.log("xytara verify:production-readiness passed");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
main();
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
// Verifies the in-memory fixed-window rate limiter (lib/rate_limiter.js) used to
|
|
4
|
-
// gate the L402 invoice-creating path. Offline, no network, no timers waited on.
|
|
5
|
-
|
|
6
|
-
const assert = require("assert");
|
|
7
|
-
const { createRateLimiter } = require("../lib/rate_limiter");
|
|
8
|
-
|
|
9
|
-
function main() {
|
|
10
|
-
// Per-key ceiling: first `max` hits allowed, the next denied with scope "key".
|
|
11
|
-
const rl = createRateLimiter({ windowMs: 60000, max: 3, maxGlobal: 100 });
|
|
12
|
-
assert.strictEqual(rl.check("1.1.1.1").allowed, true);
|
|
13
|
-
assert.strictEqual(rl.check("1.1.1.1").allowed, true);
|
|
14
|
-
const third = rl.check("1.1.1.1");
|
|
15
|
-
assert.strictEqual(third.allowed, true);
|
|
16
|
-
assert.strictEqual(third.remaining, 0);
|
|
17
|
-
const denied = rl.check("1.1.1.1");
|
|
18
|
-
assert.strictEqual(denied.allowed, false);
|
|
19
|
-
assert.strictEqual(denied.scope, "key");
|
|
20
|
-
assert.ok(denied.retryAfterMs > 0 && denied.retryAfterMs <= 60000);
|
|
21
|
-
|
|
22
|
-
// A different key is independent (not affected by another key's exhaustion).
|
|
23
|
-
assert.strictEqual(rl.check("2.2.2.2").allowed, true);
|
|
24
|
-
|
|
25
|
-
// Denied hits are not counted, so they cannot push the reset further out or
|
|
26
|
-
// starve a key beyond its window (no self-inflicted lockout extension).
|
|
27
|
-
assert.strictEqual(rl.check("1.1.1.1").allowed, false);
|
|
28
|
-
|
|
29
|
-
// Global ceiling: once the global budget is spent, a fresh key is still denied
|
|
30
|
-
// with scope "global" (maxGlobal >= max always, so set them equal to exercise it
|
|
31
|
-
// across distinct keys before any single key is exhausted).
|
|
32
|
-
const g = createRateLimiter({ windowMs: 60000, max: 2, maxGlobal: 2 });
|
|
33
|
-
assert.strictEqual(g.check("a").allowed, true); // global 1, key a 1
|
|
34
|
-
assert.strictEqual(g.check("b").allowed, true); // global 2, key b 1
|
|
35
|
-
const gDenied = g.check("c"); // global budget spent, key c untouched
|
|
36
|
-
assert.strictEqual(gDenied.allowed, false);
|
|
37
|
-
assert.strictEqual(gDenied.scope, "global");
|
|
38
|
-
|
|
39
|
-
// reset() clears all buckets.
|
|
40
|
-
g.reset();
|
|
41
|
-
assert.strictEqual(g.check("c").allowed, true);
|
|
42
|
-
|
|
43
|
-
// Config is clamped to sane floors.
|
|
44
|
-
const clamped = createRateLimiter({ windowMs: 1, max: 0, maxGlobal: 0 });
|
|
45
|
-
assert.ok(clamped.config.windowMs >= 1000);
|
|
46
|
-
assert.ok(clamped.config.max >= 1);
|
|
47
|
-
assert.ok(clamped.config.maxGlobal >= clamped.config.max);
|
|
48
|
-
|
|
49
|
-
console.log("verify_rate_limiter: all rate limiter checks passed");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
main();
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Verifies the in-memory fixed-window rate limiter (lib/rate_limiter.js) used to
|
|
4
|
+
// gate the L402 invoice-creating path. Offline, no network, no timers waited on.
|
|
5
|
+
|
|
6
|
+
const assert = require("assert");
|
|
7
|
+
const { createRateLimiter } = require("../lib/rate_limiter");
|
|
8
|
+
|
|
9
|
+
function main() {
|
|
10
|
+
// Per-key ceiling: first `max` hits allowed, the next denied with scope "key".
|
|
11
|
+
const rl = createRateLimiter({ windowMs: 60000, max: 3, maxGlobal: 100 });
|
|
12
|
+
assert.strictEqual(rl.check("1.1.1.1").allowed, true);
|
|
13
|
+
assert.strictEqual(rl.check("1.1.1.1").allowed, true);
|
|
14
|
+
const third = rl.check("1.1.1.1");
|
|
15
|
+
assert.strictEqual(third.allowed, true);
|
|
16
|
+
assert.strictEqual(third.remaining, 0);
|
|
17
|
+
const denied = rl.check("1.1.1.1");
|
|
18
|
+
assert.strictEqual(denied.allowed, false);
|
|
19
|
+
assert.strictEqual(denied.scope, "key");
|
|
20
|
+
assert.ok(denied.retryAfterMs > 0 && denied.retryAfterMs <= 60000);
|
|
21
|
+
|
|
22
|
+
// A different key is independent (not affected by another key's exhaustion).
|
|
23
|
+
assert.strictEqual(rl.check("2.2.2.2").allowed, true);
|
|
24
|
+
|
|
25
|
+
// Denied hits are not counted, so they cannot push the reset further out or
|
|
26
|
+
// starve a key beyond its window (no self-inflicted lockout extension).
|
|
27
|
+
assert.strictEqual(rl.check("1.1.1.1").allowed, false);
|
|
28
|
+
|
|
29
|
+
// Global ceiling: once the global budget is spent, a fresh key is still denied
|
|
30
|
+
// with scope "global" (maxGlobal >= max always, so set them equal to exercise it
|
|
31
|
+
// across distinct keys before any single key is exhausted).
|
|
32
|
+
const g = createRateLimiter({ windowMs: 60000, max: 2, maxGlobal: 2 });
|
|
33
|
+
assert.strictEqual(g.check("a").allowed, true); // global 1, key a 1
|
|
34
|
+
assert.strictEqual(g.check("b").allowed, true); // global 2, key b 1
|
|
35
|
+
const gDenied = g.check("c"); // global budget spent, key c untouched
|
|
36
|
+
assert.strictEqual(gDenied.allowed, false);
|
|
37
|
+
assert.strictEqual(gDenied.scope, "global");
|
|
38
|
+
|
|
39
|
+
// reset() clears all buckets.
|
|
40
|
+
g.reset();
|
|
41
|
+
assert.strictEqual(g.check("c").allowed, true);
|
|
42
|
+
|
|
43
|
+
// Config is clamped to sane floors.
|
|
44
|
+
const clamped = createRateLimiter({ windowMs: 1, max: 0, maxGlobal: 0 });
|
|
45
|
+
assert.ok(clamped.config.windowMs >= 1000);
|
|
46
|
+
assert.ok(clamped.config.max >= 1);
|
|
47
|
+
assert.ok(clamped.config.maxGlobal >= clamped.config.max);
|
|
48
|
+
|
|
49
|
+
console.log("verify_rate_limiter: all rate limiter checks passed");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
main();
|