xytara 3.1.13 → 3.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +38 -4
- package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
- package/REAL_PAYMENT_SETUP.md +14 -7
- package/RELEASE_NOTES.md +9 -4
- package/fixtures/catalog.js +22 -0
- package/lib/a2a_rpc.js +287 -280
- package/lib/account_auth.js +585 -585
- package/lib/agent_identity.js +162 -162
- package/lib/asyncapi_contract.js +3 -3
- package/lib/bolt_adapter.js +187 -187
- package/lib/bolt_payment_front.js +224 -224
- package/lib/buyer_event_log.js +215 -0
- package/lib/buyer_safety_policy.js +132 -0
- package/lib/checkout_processor_adapter.js +217 -217
- package/lib/command_flow.js +911 -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 +99 -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/openapi_contract.js +4 -0
- package/lib/payment_config.js +257 -253
- package/lib/payment_verification.js +357 -357
- package/lib/provider_payout_execution.js +155 -155
- package/lib/provider_registry.js +852 -842
- package/lib/rate_limiter.js +81 -81
- package/lib/runtime_state_store.js +495 -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 +198 -191
- package/lib/x402_lane_contract.js +5 -3
- package/lib/x402_settlement_recovery.js +175 -0
- package/package.json +9 -3
- package/scripts/buyer_events_daily_report.js +79 -0
- package/scripts/state_backup_offsite.js +196 -0
- package/scripts/verify_a2a_rpc.js +131 -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_buyer_event_log.js +123 -0
- package/scripts/verify_buyer_safety.js +141 -0
- 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_lifecycle_spend_binding.js +88 -0
- 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 +209 -208
- package/scripts/verify_x402_facilitator.js +307 -286
- package/scripts/verify_x402_settlement_recovery.js +153 -0
- package/server.js +12242 -11974
|
@@ -1,208 +1,209 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
// Verifies the don't-charge-on-failure guarantee for the x402 command path:
|
|
4
|
-
// - when external execution FAILS, the deferred x402 settlement is NEVER called
|
|
5
|
-
// (the facilitator /settle is not hit), the response is a non-2xx carrying
|
|
6
|
-
// payment_settled:false and a "you were not charged" remediation, and no
|
|
7
|
-
// PAYMENT-RESPONSE receipt is emitted;
|
|
8
|
-
// - when external execution SUCCEEDS, settlement runs exactly once AFTER the work,
|
|
9
|
-
// the response is 200 with the real tool output surfaced and a PAYMENT-RESPONSE
|
|
10
|
-
// receipt attached.
|
|
11
|
-
//
|
|
12
|
-
// This is the verify -> execute -> settle reorder: an x402 authorization is verified
|
|
13
|
-
// up front but only settled once the deliverable is produced, so a failed call costs
|
|
14
|
-
// the paying agent nothing. No real network or chain is used.
|
|
15
|
-
|
|
16
|
-
const assert = require("assert");
|
|
17
|
-
const http = require("http");
|
|
18
|
-
|
|
19
|
-
const ACCEPTS = [
|
|
20
|
-
{
|
|
21
|
-
scheme: "exact",
|
|
22
|
-
network: "eip155:84532",
|
|
23
|
-
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
24
|
-
amount: "1000",
|
|
25
|
-
payTo: "0x7cd2E0Ed2721319B20FAac4109774d030833F84D",
|
|
26
|
-
extra: { name: "USDC", version: "2" },
|
|
27
|
-
maxTimeoutSeconds: 60
|
|
28
|
-
}
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
// One mock server stands in for BOTH the x402 facilitator (/verify, /settle) and the
|
|
32
|
-
// external execution target (/exec-ok succeeds, /exec-fail rejects with a 502).
|
|
33
|
-
function startMockServer() {
|
|
34
|
-
const calls = { verify: 0, settle: 0, execOk: 0, execFail: 0 };
|
|
35
|
-
const server = http.createServer((req, res) => {
|
|
36
|
-
const chunks = [];
|
|
37
|
-
req.on("data", (c) => chunks.push(c));
|
|
38
|
-
req.on("end", () => {
|
|
39
|
-
let body = {};
|
|
40
|
-
try { body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); } catch (_) { body = {}; }
|
|
41
|
-
const reply = (payload, status) => {
|
|
42
|
-
const text = JSON.stringify(payload);
|
|
43
|
-
res.writeHead(status || 200, { "content-type": "application/json", "content-length": Buffer.byteLength(text) });
|
|
44
|
-
res.end(text);
|
|
45
|
-
};
|
|
46
|
-
if (req.method === "POST" && req.url === "/verify") {
|
|
47
|
-
calls.verify += 1;
|
|
48
|
-
return reply({ isValid: true, payer: "0x1111111111111111111111111111111111111111" });
|
|
49
|
-
}
|
|
50
|
-
if (req.method === "POST" && req.url === "/settle") {
|
|
51
|
-
calls.settle += 1;
|
|
52
|
-
const sig = body && body.paymentPayload && body.paymentPayload.payload ? String(body.paymentPayload.payload.signature || "") : "";
|
|
53
|
-
if (sig === "settle-fails") return reply({ success: false, errorReason: "insufficient_funds" });
|
|
54
|
-
return reply({ success: true, transaction: "0xmocktx", network: "eip155:84532", payer: "0x1111111111111111111111111111111111111111" });
|
|
55
|
-
}
|
|
56
|
-
if (req.method === "POST" && req.url === "/exec-ok") {
|
|
57
|
-
calls.execOk += 1;
|
|
58
|
-
return reply({ ok: true, status: "completed", output: { value: "0xdeadbeef" } });
|
|
59
|
-
}
|
|
60
|
-
if (req.method === "POST" && req.url === "/exec-fail") {
|
|
61
|
-
calls.execFail += 1;
|
|
62
|
-
return reply({ ok: false, reason: "data_required" }, 502);
|
|
63
|
-
}
|
|
64
|
-
res.writeHead(404); res.end();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, calls, port: server.address().port })));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function paymentHeader(signature) {
|
|
71
|
-
const accepted = ACCEPTS[0];
|
|
72
|
-
return Buffer.from(JSON.stringify({
|
|
73
|
-
x402Version: 2,
|
|
74
|
-
accepted,
|
|
75
|
-
payload: {
|
|
76
|
-
signature,
|
|
77
|
-
authorization: {
|
|
78
|
-
from: "0x1111111111111111111111111111111111111111",
|
|
79
|
-
to: accepted.payTo,
|
|
80
|
-
value: accepted.amount,
|
|
81
|
-
validAfter: "0",
|
|
82
|
-
validBefore: String(Math.floor(Date.now() / 1000) + 600),
|
|
83
|
-
nonce
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
process.env.
|
|
111
|
-
process.env.
|
|
112
|
-
process.env.
|
|
113
|
-
|
|
114
|
-
{ target_id: "
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
assert.strictEqual(failed.
|
|
131
|
-
assert.strictEqual(failed.json.
|
|
132
|
-
assert.
|
|
133
|
-
assert.ok(
|
|
134
|
-
assert.
|
|
135
|
-
assert.strictEqual(
|
|
136
|
-
assert.strictEqual(mock.calls.
|
|
137
|
-
assert.strictEqual(mock.calls.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
assert.strictEqual(ok.
|
|
145
|
-
assert.strictEqual(ok.json.transaction.
|
|
146
|
-
assert.strictEqual(
|
|
147
|
-
assert.strictEqual(mock.calls.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
assert.strictEqual(receipt.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
assert.strictEqual(settleFail.
|
|
160
|
-
assert.strictEqual(settleFail.json.
|
|
161
|
-
assert.
|
|
162
|
-
assert.ok(
|
|
163
|
-
assert.
|
|
164
|
-
assert.strictEqual(
|
|
165
|
-
assert.strictEqual(mock.calls.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
process.env.XYTARA_PAYMENT_VERIFICATION_MODE
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"x-
|
|
182
|
-
"x-payment
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
assert.strictEqual(presFail.json.
|
|
186
|
-
assert.
|
|
187
|
-
assert.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
await new Promise((resolve) =>
|
|
197
|
-
|
|
198
|
-
restore(
|
|
199
|
-
restore("
|
|
200
|
-
restore("
|
|
201
|
-
restore("
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Verifies the don't-charge-on-failure guarantee for the x402 command path:
|
|
4
|
+
// - when external execution FAILS, the deferred x402 settlement is NEVER called
|
|
5
|
+
// (the facilitator /settle is not hit), the response is a non-2xx carrying
|
|
6
|
+
// payment_settled:false and a "you were not charged" remediation, and no
|
|
7
|
+
// PAYMENT-RESPONSE receipt is emitted;
|
|
8
|
+
// - when external execution SUCCEEDS, settlement runs exactly once AFTER the work,
|
|
9
|
+
// the response is 200 with the real tool output surfaced and a PAYMENT-RESPONSE
|
|
10
|
+
// receipt attached.
|
|
11
|
+
//
|
|
12
|
+
// This is the verify -> execute -> settle reorder: an x402 authorization is verified
|
|
13
|
+
// up front but only settled once the deliverable is produced, so a failed call costs
|
|
14
|
+
// the paying agent nothing. No real network or chain is used.
|
|
15
|
+
|
|
16
|
+
const assert = require("assert");
|
|
17
|
+
const http = require("http");
|
|
18
|
+
|
|
19
|
+
const ACCEPTS = [
|
|
20
|
+
{
|
|
21
|
+
scheme: "exact",
|
|
22
|
+
network: "eip155:84532",
|
|
23
|
+
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
24
|
+
amount: "1000",
|
|
25
|
+
payTo: "0x7cd2E0Ed2721319B20FAac4109774d030833F84D",
|
|
26
|
+
extra: { name: "USDC", version: "2" },
|
|
27
|
+
maxTimeoutSeconds: 60
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// One mock server stands in for BOTH the x402 facilitator (/verify, /settle) and the
|
|
32
|
+
// external execution target (/exec-ok succeeds, /exec-fail rejects with a 502).
|
|
33
|
+
function startMockServer() {
|
|
34
|
+
const calls = { verify: 0, settle: 0, execOk: 0, execFail: 0 };
|
|
35
|
+
const server = http.createServer((req, res) => {
|
|
36
|
+
const chunks = [];
|
|
37
|
+
req.on("data", (c) => chunks.push(c));
|
|
38
|
+
req.on("end", () => {
|
|
39
|
+
let body = {};
|
|
40
|
+
try { body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); } catch (_) { body = {}; }
|
|
41
|
+
const reply = (payload, status) => {
|
|
42
|
+
const text = JSON.stringify(payload);
|
|
43
|
+
res.writeHead(status || 200, { "content-type": "application/json", "content-length": Buffer.byteLength(text) });
|
|
44
|
+
res.end(text);
|
|
45
|
+
};
|
|
46
|
+
if (req.method === "POST" && req.url === "/verify") {
|
|
47
|
+
calls.verify += 1;
|
|
48
|
+
return reply({ isValid: true, payer: "0x1111111111111111111111111111111111111111" });
|
|
49
|
+
}
|
|
50
|
+
if (req.method === "POST" && req.url === "/settle") {
|
|
51
|
+
calls.settle += 1;
|
|
52
|
+
const sig = body && body.paymentPayload && body.paymentPayload.payload ? String(body.paymentPayload.payload.signature || "") : "";
|
|
53
|
+
if (sig === "settle-fails") return reply({ success: false, errorReason: "insufficient_funds" });
|
|
54
|
+
return reply({ success: true, transaction: "0xmocktx", network: "eip155:84532", payer: "0x1111111111111111111111111111111111111111" });
|
|
55
|
+
}
|
|
56
|
+
if (req.method === "POST" && req.url === "/exec-ok") {
|
|
57
|
+
calls.execOk += 1;
|
|
58
|
+
return reply({ ok: true, status: "completed", output: { value: "0xdeadbeef" } });
|
|
59
|
+
}
|
|
60
|
+
if (req.method === "POST" && req.url === "/exec-fail") {
|
|
61
|
+
calls.execFail += 1;
|
|
62
|
+
return reply({ ok: false, reason: "data_required" }, 502);
|
|
63
|
+
}
|
|
64
|
+
res.writeHead(404); res.end();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, calls, port: server.address().port })));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function paymentHeader(signature) {
|
|
71
|
+
const accepted = ACCEPTS[0];
|
|
72
|
+
return Buffer.from(JSON.stringify({
|
|
73
|
+
x402Version: 2,
|
|
74
|
+
accepted,
|
|
75
|
+
payload: {
|
|
76
|
+
signature,
|
|
77
|
+
authorization: {
|
|
78
|
+
from: "0x1111111111111111111111111111111111111111",
|
|
79
|
+
to: accepted.payTo,
|
|
80
|
+
value: accepted.amount,
|
|
81
|
+
validAfter: "0",
|
|
82
|
+
validBefore: String(Math.floor(Date.now() / 1000) + 600),
|
|
83
|
+
// A fresh nonce per payment, as a real wallet signs; a reused nonce is a replay.
|
|
84
|
+
nonce: `0x${require("crypto").randomBytes(32).toString("hex")}`
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}), "utf8").toString("base64");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function post(baseUrl, path, body, headers = {}) {
|
|
91
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "content-type": "application/json", ...headers },
|
|
94
|
+
body: JSON.stringify(body)
|
|
95
|
+
});
|
|
96
|
+
let json = null;
|
|
97
|
+
try { json = await res.json(); } catch (_) { json = null; }
|
|
98
|
+
return { status: res.status, json, headers: res.headers };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function main() {
|
|
102
|
+
const mock = await startMockServer();
|
|
103
|
+
const mockBase = `http://127.0.0.1:${mock.port}`;
|
|
104
|
+
const previous = {
|
|
105
|
+
mode: process.env.XYTARA_PAYMENT_VERIFICATION_MODE,
|
|
106
|
+
accepts: process.env.XYTARA_X402_ACCEPTS_JSON,
|
|
107
|
+
facUrl: process.env.XYTARA_X402_FACILITATOR_URL,
|
|
108
|
+
targets: process.env.XYTARA_EXECUTION_TARGETS_JSON
|
|
109
|
+
};
|
|
110
|
+
process.env.XYTARA_PAYMENT_VERIFICATION_MODE = "x402_facilitator";
|
|
111
|
+
process.env.XYTARA_X402_ACCEPTS_JSON = JSON.stringify(ACCEPTS);
|
|
112
|
+
process.env.XYTARA_X402_FACILITATOR_URL = mockBase;
|
|
113
|
+
process.env.XYTARA_EXECUTION_TARGETS_JSON = JSON.stringify([
|
|
114
|
+
{ target_id: "exec_ok", task_ref: "exec.ok", endpoint_url: `${mockBase}/exec-ok` },
|
|
115
|
+
{ target_id: "exec_fail", task_ref: "exec.fail", endpoint_url: `${mockBase}/exec-fail` }
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
delete require.cache[require.resolve("../server")];
|
|
119
|
+
const { createServer } = require("../server");
|
|
120
|
+
const server = createServer();
|
|
121
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
122
|
+
const baseUrl = `http://127.0.0.1:${server.address().port}`;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// 1. Execution FAILS -> deferred settlement is never reached; caller is not charged.
|
|
126
|
+
const settleBeforeFail = mock.calls.settle;
|
|
127
|
+
const verifyBeforeFail = mock.calls.verify;
|
|
128
|
+
const failBody = { account_id: "acct_ves", command: "failing execution", task_ref: "exec.fail", body: { subject_id: "s-1" } };
|
|
129
|
+
const failed = await post(baseUrl, "/x402/commands/execute", failBody, { "payment-signature": paymentHeader("ok") });
|
|
130
|
+
assert.strictEqual(failed.status, 502, "failed execution must return 502");
|
|
131
|
+
assert.strictEqual(failed.json.ok, false, "failed execution must report ok:false");
|
|
132
|
+
assert.strictEqual(failed.json.payment_settled, false, "a failed x402 call must NOT be settled");
|
|
133
|
+
assert.ok(/not charged/i.test(String(failed.json.remediation || "")), "failed x402 call must tell the agent it was not charged");
|
|
134
|
+
assert.ok(!failed.json.settlement, "failed x402 call must carry no settlement");
|
|
135
|
+
assert.strictEqual(failed.headers.get("payment-response"), null, "failed x402 call must NOT emit a PAYMENT-RESPONSE receipt");
|
|
136
|
+
assert.strictEqual(mock.calls.settle, settleBeforeFail, "facilitator /settle must NOT be called when execution fails");
|
|
137
|
+
assert.strictEqual(mock.calls.verify, verifyBeforeFail + 1, "the payment must still have been verified up front");
|
|
138
|
+
assert.strictEqual(mock.calls.execFail, 1, "the failing execution target must have been attempted");
|
|
139
|
+
|
|
140
|
+
// 2. Execution SUCCEEDS -> settle runs exactly once, after the work, with a receipt.
|
|
141
|
+
const settleBeforeOk = mock.calls.settle;
|
|
142
|
+
const okBody = { account_id: "acct_ves", command: "successful execution", task_ref: "exec.ok", body: { subject_id: "s-2" } };
|
|
143
|
+
const ok = await post(baseUrl, "/x402/commands/execute", okBody, { "payment-signature": paymentHeader("ok") });
|
|
144
|
+
assert.strictEqual(ok.status, 200, "successful execution must return 200");
|
|
145
|
+
assert.strictEqual(ok.json.transaction.status, "completed", "successful transaction must be completed");
|
|
146
|
+
assert.strictEqual(ok.json.transaction.payment.verification_mode, "x402_facilitator", "verification mode must be recorded");
|
|
147
|
+
assert.strictEqual(mock.calls.settle, settleBeforeOk + 1, "facilitator /settle must be called exactly once on success");
|
|
148
|
+
assert.strictEqual(mock.calls.execOk, 1, "the successful execution target must have been invoked");
|
|
149
|
+
const receiptHeader = ok.headers.get("payment-response");
|
|
150
|
+
assert.strictEqual(typeof receiptHeader, "string", "a settled success must carry a PAYMENT-RESPONSE receipt");
|
|
151
|
+
const receipt = JSON.parse(Buffer.from(receiptHeader, "base64").toString("utf8"));
|
|
152
|
+
assert.strictEqual(receipt.success, true, "settlement receipt must report success");
|
|
153
|
+
assert.strictEqual(receipt.transaction, "0xmocktx", "settlement receipt transaction mismatch");
|
|
154
|
+
|
|
155
|
+
// 3. Settle FAILS after a SUCCESSFUL execution -> deliverable withheld, not charged.
|
|
156
|
+
const settleBeforeSf = mock.calls.settle;
|
|
157
|
+
const sfBody = { account_id: "acct_ves", command: "settle fails after success", task_ref: "exec.ok", body: { subject_id: "s-3" } };
|
|
158
|
+
const settleFail = await post(baseUrl, "/x402/commands/execute", sfBody, { "payment-signature": paymentHeader("settle-fails") });
|
|
159
|
+
assert.strictEqual(settleFail.status, 402, "a failed post-execution settlement must 402");
|
|
160
|
+
assert.strictEqual(settleFail.json.ok, false, "a failed settlement must report ok:false");
|
|
161
|
+
assert.strictEqual(settleFail.json.payment_settled, false, "a failed settlement must report payment_settled:false");
|
|
162
|
+
assert.ok(/not charged/i.test(String(settleFail.json.remediation || "")), "settle-fail must tell the agent it was not charged");
|
|
163
|
+
assert.ok(!settleFail.json.transaction, "settle-fail must NOT return the deliverable");
|
|
164
|
+
assert.strictEqual(settleFail.headers.get("payment-response"), null, "settle-fail must emit no receipt");
|
|
165
|
+
assert.strictEqual(mock.calls.settle, settleBeforeSf + 1, "settle must have been attempted once");
|
|
166
|
+
assert.strictEqual(mock.calls.execOk, 2, "the work must have executed before the settle attempt");
|
|
167
|
+
|
|
168
|
+
// 4. Non-x402 lane (presence mode, no on-chain settlement) that fails execution must
|
|
169
|
+
// NOT claim the caller was charged and must NOT point them at a refund flow — the
|
|
170
|
+
// failure flag is keyed on an actual receipt, not merely on "not deferred".
|
|
171
|
+
await new Promise((resolve) => server.close(resolve));
|
|
172
|
+
const prevMode = process.env.XYTARA_PAYMENT_VERIFICATION_MODE;
|
|
173
|
+
process.env.XYTARA_PAYMENT_VERIFICATION_MODE = "presence";
|
|
174
|
+
delete require.cache[require.resolve("../server")];
|
|
175
|
+
const presenceServerFactory = require("../server").createServer;
|
|
176
|
+
const presenceServer = presenceServerFactory();
|
|
177
|
+
await new Promise((resolve) => presenceServer.listen(0, "127.0.0.1", resolve));
|
|
178
|
+
const presenceBase = `http://127.0.0.1:${presenceServer.address().port}`;
|
|
179
|
+
try {
|
|
180
|
+
const presFail = await post(presenceBase, "/v1/commands/execute", { account_id: "acct_ves", command: "presence failing exec", task_ref: "exec.fail", body: { subject_id: "s-4" } }, {
|
|
181
|
+
"x-wallet-id": "wallet-presence",
|
|
182
|
+
"x-payment": "encoded",
|
|
183
|
+
"x-payment-signature": "sig"
|
|
184
|
+
});
|
|
185
|
+
assert.strictEqual(presFail.json.ok, false, "presence failing execution must report ok:false");
|
|
186
|
+
assert.strictEqual(presFail.json.payment_settled, false, "presence lane must NOT claim a charge on failure");
|
|
187
|
+
assert.ok(!/refund/i.test(String(presFail.json.remediation || "")), "presence lane must NOT point to a refund flow on failure");
|
|
188
|
+
assert.strictEqual(presFail.headers.get("payment-response"), null, "presence failure must emit no receipt");
|
|
189
|
+
} finally {
|
|
190
|
+
await new Promise((resolve) => presenceServer.close(resolve));
|
|
191
|
+
if (prevMode === undefined) delete process.env.XYTARA_PAYMENT_VERIFICATION_MODE; else process.env.XYTARA_PAYMENT_VERIFICATION_MODE = prevMode;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log("xytara verify:verify-execute-settle passed");
|
|
195
|
+
} finally {
|
|
196
|
+
await new Promise((resolve) => server.close(resolve));
|
|
197
|
+
await new Promise((resolve) => mock.server.close(resolve));
|
|
198
|
+
const restore = (key, value) => { if (value === undefined) delete process.env[key]; else process.env[key] = value; };
|
|
199
|
+
restore("XYTARA_PAYMENT_VERIFICATION_MODE", previous.mode);
|
|
200
|
+
restore("XYTARA_X402_ACCEPTS_JSON", previous.accepts);
|
|
201
|
+
restore("XYTARA_X402_FACILITATOR_URL", previous.facUrl);
|
|
202
|
+
restore("XYTARA_EXECUTION_TARGETS_JSON", previous.targets);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
main().catch((error) => {
|
|
207
|
+
console.error(error && error.message ? error.message : error);
|
|
208
|
+
process.exitCode = 1;
|
|
209
|
+
});
|