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,106 +1,106 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
// Verifies credit-based refund settlement: POST /v1/refunds/<id>/settle-credit returns
|
|
4
|
-
// value to the paying account as re-spendable CREDITS (never an autonomous outbound
|
|
5
|
-
// transfer). Covers: credit-bridge auth is mandatory (403 unset/wrong), refund_units is
|
|
6
|
-
// required (400), the account balance actually increases by the credited units, the
|
|
7
|
-
// operation is idempotent per refund (no double-mint on replay), a terminal refund
|
|
8
|
-
// cannot be re-settled, and the existing refund-action route is unchanged (non-breaking).
|
|
9
|
-
|
|
10
|
-
const assert = require("assert");
|
|
11
|
-
const http = require("http");
|
|
12
|
-
|
|
13
|
-
async function req(baseUrl, method, path, body, headers = {}) {
|
|
14
|
-
const res = await fetch(`${baseUrl}${path}`, {
|
|
15
|
-
method,
|
|
16
|
-
headers: { "content-type": "application/json", ...headers },
|
|
17
|
-
...(body !== undefined ? { body: JSON.stringify(body) } : {})
|
|
18
|
-
});
|
|
19
|
-
let json = null;
|
|
20
|
-
try { json = await res.json(); } catch (_) { json = null; }
|
|
21
|
-
return { status: res.status, json };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function availableUnits(balanceJson) {
|
|
25
|
-
if (!balanceJson) return 0;
|
|
26
|
-
if (typeof balanceJson.available_units === "number") return balanceJson.available_units;
|
|
27
|
-
if (balanceJson.balance && typeof balanceJson.balance.available_units === "number") return balanceJson.balance.available_units;
|
|
28
|
-
if (balanceJson.lifecycle_totals && typeof balanceJson.lifecycle_totals.available_units === "number") return balanceJson.lifecycle_totals.available_units;
|
|
29
|
-
return 0;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function main() {
|
|
33
|
-
const prevToken = process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN;
|
|
34
|
-
const TOKEN = "test-credit-bridge-token";
|
|
35
|
-
process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN = TOKEN;
|
|
36
|
-
const auth = { authorization: `Bearer ${TOKEN}` };
|
|
37
|
-
|
|
38
|
-
delete require.cache[require.resolve("../server")];
|
|
39
|
-
const { createServer } = require("../server");
|
|
40
|
-
const server = createServer();
|
|
41
|
-
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
42
|
-
const baseUrl = `http://127.0.0.1:${server.address().port}`;
|
|
43
|
-
const ACCOUNT = "acct_refund_credit_test";
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
// Create a refund (operator case).
|
|
47
|
-
let r = await req(baseUrl, "POST", "/v1/refunds", { reason_code: "service_failure", note: "test", requested_by: "operator" });
|
|
48
|
-
assert.ok(r.json && r.json.refund && r.json.refund.refund_id, "refund created");
|
|
49
|
-
const refundId = r.json.refund.refund_id;
|
|
50
|
-
|
|
51
|
-
const before = availableUnits((await req(baseUrl, "GET", `/v1/economics/accounts/${ACCOUNT}/credit-balance`)).json);
|
|
52
|
-
|
|
53
|
-
// 1. Auth mandatory: no bearer -> 403.
|
|
54
|
-
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT, refund_units: 50 });
|
|
55
|
-
assert.strictEqual(r.status, 403, "settle-credit without bearer must 403");
|
|
56
|
-
|
|
57
|
-
// 2. Wrong bearer -> 403.
|
|
58
|
-
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT, refund_units: 50 }, { authorization: "Bearer wrong" });
|
|
59
|
-
assert.strictEqual(r.status, 403, "settle-credit with wrong bearer must 403");
|
|
60
|
-
|
|
61
|
-
// 3. refund_units required -> 400.
|
|
62
|
-
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT }, auth);
|
|
63
|
-
assert.strictEqual(r.status, 400, "missing refund_units must 400");
|
|
64
|
-
assert.strictEqual(r.json.reason, "refund_units_required", "400 reason");
|
|
65
|
-
|
|
66
|
-
// 4. Authorized settle -> 200, credit minted, refund completed.
|
|
67
|
-
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT, refund_units: 50, reason_code: "service_failure" }, auth);
|
|
68
|
-
assert.strictEqual(r.status, 200, "authorized settle-credit must 200");
|
|
69
|
-
assert.strictEqual(r.json.ok, true, "settle ok");
|
|
70
|
-
assert.strictEqual(r.json.units, 50, "credited units echoed");
|
|
71
|
-
assert.ok(r.json.credit_grant_id && r.json.credit_grant_id.startsWith("refund_credit_"), "idempotent grant id");
|
|
72
|
-
assert.strictEqual(r.json.refund.status, "completed", "refund marked completed");
|
|
73
|
-
|
|
74
|
-
// 5. Balance actually increased by the credited units.
|
|
75
|
-
const after = availableUnits((await req(baseUrl, "GET", `/v1/economics/accounts/${ACCOUNT}/credit-balance`)).json);
|
|
76
|
-
assert.strictEqual(after - before, 50, `balance must increase by 50 (before=${before}, after=${after})`);
|
|
77
|
-
|
|
78
|
-
// 6. Idempotent: a replayed settle must NOT mint twice.
|
|
79
|
-
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT, refund_units: 50 }, auth);
|
|
80
|
-
assert.strictEqual(r.status, 200, "replay still 200");
|
|
81
|
-
assert.strictEqual(r.json.already_settled, true, "replay reports already_settled");
|
|
82
|
-
const afterReplay = availableUnits((await req(baseUrl, "GET", `/v1/economics/accounts/${ACCOUNT}/credit-balance`)).json);
|
|
83
|
-
assert.strictEqual(afterReplay, after, "replay must not mint credits twice");
|
|
84
|
-
|
|
85
|
-
// 7. A terminal (completed) refund cannot be re-settled under a fresh account.
|
|
86
|
-
// (Covered by idempotency above — the completed refund returns already_settled
|
|
87
|
-
// for the original grant rather than minting to a different account.)
|
|
88
|
-
|
|
89
|
-
// 8. Non-breaking: the existing refund-action route still works on a NEW refund.
|
|
90
|
-
const r2 = await req(baseUrl, "POST", "/v1/refunds", { reason_code: "other" });
|
|
91
|
-
const refundId2 = r2.json.refund.refund_id;
|
|
92
|
-
r = await req(baseUrl, "POST", `/v1/refunds/${refundId2}/actions`, { action: "review", note: "reviewing" });
|
|
93
|
-
assert.strictEqual(r.status, 200, "existing refund-action route still works");
|
|
94
|
-
assert.strictEqual(r.json.refund.status, "under_review", "action transitioned status");
|
|
95
|
-
|
|
96
|
-
console.log("xytara verify:refund-credit passed");
|
|
97
|
-
} finally {
|
|
98
|
-
await new Promise((resolve) => server.close(resolve));
|
|
99
|
-
if (prevToken === undefined) delete process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN; else process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN = prevToken;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
main().catch((error) => {
|
|
104
|
-
console.error(error && error.message ? error.message : error);
|
|
105
|
-
process.exitCode = 1;
|
|
106
|
-
});
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Verifies credit-based refund settlement: POST /v1/refunds/<id>/settle-credit returns
|
|
4
|
+
// value to the paying account as re-spendable CREDITS (never an autonomous outbound
|
|
5
|
+
// transfer). Covers: credit-bridge auth is mandatory (403 unset/wrong), refund_units is
|
|
6
|
+
// required (400), the account balance actually increases by the credited units, the
|
|
7
|
+
// operation is idempotent per refund (no double-mint on replay), a terminal refund
|
|
8
|
+
// cannot be re-settled, and the existing refund-action route is unchanged (non-breaking).
|
|
9
|
+
|
|
10
|
+
const assert = require("assert");
|
|
11
|
+
const http = require("http");
|
|
12
|
+
|
|
13
|
+
async function req(baseUrl, method, path, body, headers = {}) {
|
|
14
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
15
|
+
method,
|
|
16
|
+
headers: { "content-type": "application/json", ...headers },
|
|
17
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {})
|
|
18
|
+
});
|
|
19
|
+
let json = null;
|
|
20
|
+
try { json = await res.json(); } catch (_) { json = null; }
|
|
21
|
+
return { status: res.status, json };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function availableUnits(balanceJson) {
|
|
25
|
+
if (!balanceJson) return 0;
|
|
26
|
+
if (typeof balanceJson.available_units === "number") return balanceJson.available_units;
|
|
27
|
+
if (balanceJson.balance && typeof balanceJson.balance.available_units === "number") return balanceJson.balance.available_units;
|
|
28
|
+
if (balanceJson.lifecycle_totals && typeof balanceJson.lifecycle_totals.available_units === "number") return balanceJson.lifecycle_totals.available_units;
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
const prevToken = process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN;
|
|
34
|
+
const TOKEN = "test-credit-bridge-token";
|
|
35
|
+
process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN = TOKEN;
|
|
36
|
+
const auth = { authorization: `Bearer ${TOKEN}` };
|
|
37
|
+
|
|
38
|
+
delete require.cache[require.resolve("../server")];
|
|
39
|
+
const { createServer } = require("../server");
|
|
40
|
+
const server = createServer();
|
|
41
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
42
|
+
const baseUrl = `http://127.0.0.1:${server.address().port}`;
|
|
43
|
+
const ACCOUNT = "acct_refund_credit_test";
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// Create a refund (operator case).
|
|
47
|
+
let r = await req(baseUrl, "POST", "/v1/refunds", { reason_code: "service_failure", note: "test", requested_by: "operator" });
|
|
48
|
+
assert.ok(r.json && r.json.refund && r.json.refund.refund_id, "refund created");
|
|
49
|
+
const refundId = r.json.refund.refund_id;
|
|
50
|
+
|
|
51
|
+
const before = availableUnits((await req(baseUrl, "GET", `/v1/economics/accounts/${ACCOUNT}/credit-balance`)).json);
|
|
52
|
+
|
|
53
|
+
// 1. Auth mandatory: no bearer -> 403.
|
|
54
|
+
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT, refund_units: 50 });
|
|
55
|
+
assert.strictEqual(r.status, 403, "settle-credit without bearer must 403");
|
|
56
|
+
|
|
57
|
+
// 2. Wrong bearer -> 403.
|
|
58
|
+
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT, refund_units: 50 }, { authorization: "Bearer wrong" });
|
|
59
|
+
assert.strictEqual(r.status, 403, "settle-credit with wrong bearer must 403");
|
|
60
|
+
|
|
61
|
+
// 3. refund_units required -> 400.
|
|
62
|
+
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT }, auth);
|
|
63
|
+
assert.strictEqual(r.status, 400, "missing refund_units must 400");
|
|
64
|
+
assert.strictEqual(r.json.reason, "refund_units_required", "400 reason");
|
|
65
|
+
|
|
66
|
+
// 4. Authorized settle -> 200, credit minted, refund completed.
|
|
67
|
+
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT, refund_units: 50, reason_code: "service_failure" }, auth);
|
|
68
|
+
assert.strictEqual(r.status, 200, "authorized settle-credit must 200");
|
|
69
|
+
assert.strictEqual(r.json.ok, true, "settle ok");
|
|
70
|
+
assert.strictEqual(r.json.units, 50, "credited units echoed");
|
|
71
|
+
assert.ok(r.json.credit_grant_id && r.json.credit_grant_id.startsWith("refund_credit_"), "idempotent grant id");
|
|
72
|
+
assert.strictEqual(r.json.refund.status, "completed", "refund marked completed");
|
|
73
|
+
|
|
74
|
+
// 5. Balance actually increased by the credited units.
|
|
75
|
+
const after = availableUnits((await req(baseUrl, "GET", `/v1/economics/accounts/${ACCOUNT}/credit-balance`)).json);
|
|
76
|
+
assert.strictEqual(after - before, 50, `balance must increase by 50 (before=${before}, after=${after})`);
|
|
77
|
+
|
|
78
|
+
// 6. Idempotent: a replayed settle must NOT mint twice.
|
|
79
|
+
r = await req(baseUrl, "POST", `/v1/refunds/${refundId}/settle-credit`, { account_id: ACCOUNT, refund_units: 50 }, auth);
|
|
80
|
+
assert.strictEqual(r.status, 200, "replay still 200");
|
|
81
|
+
assert.strictEqual(r.json.already_settled, true, "replay reports already_settled");
|
|
82
|
+
const afterReplay = availableUnits((await req(baseUrl, "GET", `/v1/economics/accounts/${ACCOUNT}/credit-balance`)).json);
|
|
83
|
+
assert.strictEqual(afterReplay, after, "replay must not mint credits twice");
|
|
84
|
+
|
|
85
|
+
// 7. A terminal (completed) refund cannot be re-settled under a fresh account.
|
|
86
|
+
// (Covered by idempotency above — the completed refund returns already_settled
|
|
87
|
+
// for the original grant rather than minting to a different account.)
|
|
88
|
+
|
|
89
|
+
// 8. Non-breaking: the existing refund-action route still works on a NEW refund.
|
|
90
|
+
const r2 = await req(baseUrl, "POST", "/v1/refunds", { reason_code: "other" });
|
|
91
|
+
const refundId2 = r2.json.refund.refund_id;
|
|
92
|
+
r = await req(baseUrl, "POST", `/v1/refunds/${refundId2}/actions`, { action: "review", note: "reviewing" });
|
|
93
|
+
assert.strictEqual(r.status, 200, "existing refund-action route still works");
|
|
94
|
+
assert.strictEqual(r.json.refund.status, "under_review", "action transitioned status");
|
|
95
|
+
|
|
96
|
+
console.log("xytara verify:refund-credit passed");
|
|
97
|
+
} finally {
|
|
98
|
+
await new Promise((resolve) => server.close(resolve));
|
|
99
|
+
if (prevToken === undefined) delete process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN; else process.env.XYTARA_CREDIT_BRIDGE_BEARER_TOKEN = prevToken;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
main().catch((error) => {
|
|
104
|
+
console.error(error && error.message ? error.message : error);
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
});
|
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const assert = require("assert");
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
const { execFileSync, execSync } = require("child_process");
|
|
7
|
-
const packageJson = require("../package.json");
|
|
8
|
-
|
|
9
|
-
function parsePackDryRun() {
|
|
10
|
-
const output = execSync("npm pack --dry-run --json", {
|
|
11
|
-
cwd: path.resolve(__dirname, ".."),
|
|
12
|
-
encoding: "utf8"
|
|
13
|
-
});
|
|
14
|
-
const parsed = JSON.parse(output);
|
|
15
|
-
return Array.isArray(parsed) ? parsed[0] : parsed;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function verifyReleaseNotes() {
|
|
19
|
-
const notesPath = path.resolve(__dirname, "..", "RELEASE_NOTES.md");
|
|
20
|
-
const notes = fs.readFileSync(notesPath, "utf8");
|
|
21
|
-
assert.strictEqual(
|
|
22
|
-
/^#\s+xytara\s+\d+\.\d+\.\d+\s+Release Notes\s*$/m.test(notes),
|
|
23
|
-
false,
|
|
24
|
-
"RELEASE_NOTES.md must not duplicate the GitHub release title as a heading"
|
|
25
|
-
);
|
|
26
|
-
assert.strictEqual(
|
|
27
|
-
notes.includes(`xytara\` ${packageJson.version}`),
|
|
28
|
-
true,
|
|
29
|
-
"RELEASE_NOTES.md must mention the current package version"
|
|
30
|
-
);
|
|
31
|
-
const mentionedVersions = notes.match(/\b\d+\.\d+\.\d+\b/g) || [];
|
|
32
|
-
assert.strictEqual(
|
|
33
|
-
mentionedVersions.every((version) => version === packageJson.version),
|
|
34
|
-
true,
|
|
35
|
-
"RELEASE_NOTES.md must not mention prior package versions"
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function main() {
|
|
40
|
-
const releaseOutput = execFileSync(process.execPath, [
|
|
41
|
-
path.resolve(__dirname, "..", "bin", "xytara-release.js"),
|
|
42
|
-
"--candidate",
|
|
43
|
-
"--summary"
|
|
44
|
-
], {
|
|
45
|
-
cwd: path.resolve(__dirname, ".."),
|
|
46
|
-
encoding: "utf8"
|
|
47
|
-
});
|
|
48
|
-
const releaseCandidate = JSON.parse(releaseOutput);
|
|
49
|
-
assert.strictEqual(releaseCandidate.product, "xytara", "release candidate product mismatch");
|
|
50
|
-
assert.strictEqual(releaseCandidate.publish_access, "public", "release candidate publish access mismatch");
|
|
51
|
-
assert.strictEqual(releaseCandidate.checklist_count >= 5, true, "release candidate checklist count mismatch");
|
|
52
|
-
verifyReleaseNotes();
|
|
53
|
-
|
|
54
|
-
const dryRun = parsePackDryRun();
|
|
55
|
-
assert.strictEqual(dryRun.name, packageJson.name, "npm pack dry-run name mismatch");
|
|
56
|
-
assert.strictEqual(dryRun.version, packageJson.version, "npm pack dry-run version mismatch");
|
|
57
|
-
assert.strictEqual(Array.isArray(dryRun.files), true, "npm pack dry-run file list missing");
|
|
58
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "bin/xytara-run.js"), true, "npm pack dry-run missing xytara-run");
|
|
59
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "bin/xytara-first-run.js"), true, "npm pack dry-run missing xytara-first-run");
|
|
60
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "bin/xytara-release.js"), true, "npm pack dry-run missing xytara-release");
|
|
61
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_release_candidate.js"), true, "npm pack dry-run missing release verifier script");
|
|
62
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_framework_provider_promotion.js"), true, "npm pack dry-run missing framework provider promotion verifier script");
|
|
63
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_operator_observability_boundary.js"), true, "npm pack dry-run missing operator observability boundary verifier script");
|
|
64
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_pricing_experiment_plan.js"), true, "npm pack dry-run missing pricing experiment verifier script");
|
|
65
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_treasury_public_boundary.js"), true, "npm pack dry-run missing treasury public boundary verifier script");
|
|
66
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_full_catalog.js"), true, "npm pack dry-run missing full catalog verifier script");
|
|
67
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_all.js"), true, "npm pack dry-run missing package verifier script");
|
|
68
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "lib/framework_provider_promotion.js"), true, "npm pack dry-run missing framework provider promotion pack");
|
|
69
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "README.md"), true, "npm pack dry-run missing README");
|
|
70
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "RELEASE_NOTES.md"), true, "npm pack dry-run missing release notes");
|
|
71
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "START_HERE.md"), true, "npm pack dry-run missing start-here");
|
|
72
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "SERVICE_CONTRACT.md"), true, "npm pack dry-run missing service contract");
|
|
73
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "OPERATOR_START_HERE.md"), false, "npm pack dry-run should not ship operator start-here");
|
|
74
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "OPERATIONS_RUNBOOK.md"), false, "npm pack dry-run should not ship operations runbook");
|
|
75
|
-
assert.strictEqual(dryRun.files.some((entry) => entry.path === "PUBLISH_PLAN.md"), false, "npm pack dry-run should not ship publish plan");
|
|
76
|
-
assert.strictEqual(dryRun.files.length >= 10, true, "npm pack dry-run file count too small");
|
|
77
|
-
|
|
78
|
-
console.log("xytara verify:release-candidate passed");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
main();
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const assert = require("assert");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { execFileSync, execSync } = require("child_process");
|
|
7
|
+
const packageJson = require("../package.json");
|
|
8
|
+
|
|
9
|
+
function parsePackDryRun() {
|
|
10
|
+
const output = execSync("npm pack --dry-run --json", {
|
|
11
|
+
cwd: path.resolve(__dirname, ".."),
|
|
12
|
+
encoding: "utf8"
|
|
13
|
+
});
|
|
14
|
+
const parsed = JSON.parse(output);
|
|
15
|
+
return Array.isArray(parsed) ? parsed[0] : parsed;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function verifyReleaseNotes() {
|
|
19
|
+
const notesPath = path.resolve(__dirname, "..", "RELEASE_NOTES.md");
|
|
20
|
+
const notes = fs.readFileSync(notesPath, "utf8");
|
|
21
|
+
assert.strictEqual(
|
|
22
|
+
/^#\s+xytara\s+\d+\.\d+\.\d+\s+Release Notes\s*$/m.test(notes),
|
|
23
|
+
false,
|
|
24
|
+
"RELEASE_NOTES.md must not duplicate the GitHub release title as a heading"
|
|
25
|
+
);
|
|
26
|
+
assert.strictEqual(
|
|
27
|
+
notes.includes(`xytara\` ${packageJson.version}`),
|
|
28
|
+
true,
|
|
29
|
+
"RELEASE_NOTES.md must mention the current package version"
|
|
30
|
+
);
|
|
31
|
+
const mentionedVersions = notes.match(/\b\d+\.\d+\.\d+\b/g) || [];
|
|
32
|
+
assert.strictEqual(
|
|
33
|
+
mentionedVersions.every((version) => version === packageJson.version),
|
|
34
|
+
true,
|
|
35
|
+
"RELEASE_NOTES.md must not mention prior package versions"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function main() {
|
|
40
|
+
const releaseOutput = execFileSync(process.execPath, [
|
|
41
|
+
path.resolve(__dirname, "..", "bin", "xytara-release.js"),
|
|
42
|
+
"--candidate",
|
|
43
|
+
"--summary"
|
|
44
|
+
], {
|
|
45
|
+
cwd: path.resolve(__dirname, ".."),
|
|
46
|
+
encoding: "utf8"
|
|
47
|
+
});
|
|
48
|
+
const releaseCandidate = JSON.parse(releaseOutput);
|
|
49
|
+
assert.strictEqual(releaseCandidate.product, "xytara", "release candidate product mismatch");
|
|
50
|
+
assert.strictEqual(releaseCandidate.publish_access, "public", "release candidate publish access mismatch");
|
|
51
|
+
assert.strictEqual(releaseCandidate.checklist_count >= 5, true, "release candidate checklist count mismatch");
|
|
52
|
+
verifyReleaseNotes();
|
|
53
|
+
|
|
54
|
+
const dryRun = parsePackDryRun();
|
|
55
|
+
assert.strictEqual(dryRun.name, packageJson.name, "npm pack dry-run name mismatch");
|
|
56
|
+
assert.strictEqual(dryRun.version, packageJson.version, "npm pack dry-run version mismatch");
|
|
57
|
+
assert.strictEqual(Array.isArray(dryRun.files), true, "npm pack dry-run file list missing");
|
|
58
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "bin/xytara-run.js"), true, "npm pack dry-run missing xytara-run");
|
|
59
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "bin/xytara-first-run.js"), true, "npm pack dry-run missing xytara-first-run");
|
|
60
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "bin/xytara-release.js"), true, "npm pack dry-run missing xytara-release");
|
|
61
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_release_candidate.js"), true, "npm pack dry-run missing release verifier script");
|
|
62
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_framework_provider_promotion.js"), true, "npm pack dry-run missing framework provider promotion verifier script");
|
|
63
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_operator_observability_boundary.js"), true, "npm pack dry-run missing operator observability boundary verifier script");
|
|
64
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_pricing_experiment_plan.js"), true, "npm pack dry-run missing pricing experiment verifier script");
|
|
65
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_treasury_public_boundary.js"), true, "npm pack dry-run missing treasury public boundary verifier script");
|
|
66
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_full_catalog.js"), true, "npm pack dry-run missing full catalog verifier script");
|
|
67
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "scripts/verify_all.js"), true, "npm pack dry-run missing package verifier script");
|
|
68
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "lib/framework_provider_promotion.js"), true, "npm pack dry-run missing framework provider promotion pack");
|
|
69
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "README.md"), true, "npm pack dry-run missing README");
|
|
70
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "RELEASE_NOTES.md"), true, "npm pack dry-run missing release notes");
|
|
71
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "START_HERE.md"), true, "npm pack dry-run missing start-here");
|
|
72
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "SERVICE_CONTRACT.md"), true, "npm pack dry-run missing service contract");
|
|
73
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "OPERATOR_START_HERE.md"), false, "npm pack dry-run should not ship operator start-here");
|
|
74
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "OPERATIONS_RUNBOOK.md"), false, "npm pack dry-run should not ship operations runbook");
|
|
75
|
+
assert.strictEqual(dryRun.files.some((entry) => entry.path === "PUBLISH_PLAN.md"), false, "npm pack dry-run should not ship publish plan");
|
|
76
|
+
assert.strictEqual(dryRun.files.length >= 10, true, "npm pack dry-run file count too small");
|
|
77
|
+
|
|
78
|
+
console.log("xytara verify:release-candidate passed");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Verifies the runtime-state WRITE GUARD: when the boot-time restore fails (backend 504), the store
|
|
4
|
+
// must never write (a write would overwrite the good snapshot with near-empty state). It retries the
|
|
5
|
+
// restore, merges the durable snapshot under whatever happened since boot, and only then writes.
|
|
6
|
+
|
|
7
|
+
const assert = require("assert");
|
|
8
|
+
const http = require("http");
|
|
9
|
+
const { createRuntimeStateStore } = require("../lib/runtime_state_store");
|
|
10
|
+
|
|
11
|
+
function startFakeSupabase() {
|
|
12
|
+
const calls = { get: 0, post: 0, lastPostBody: null, keys: [] };
|
|
13
|
+
let failReads = true;
|
|
14
|
+
const durable = {
|
|
15
|
+
providerRegistry: { __runtime_state_kind: "Map", value: [["mcp_executor", { provider_id: "mcp_executor", status: "active" }]] },
|
|
16
|
+
providerPayouts: [{ payout_id: "provpay_1", status: "settled" }],
|
|
17
|
+
acceptedPaymentNonces: { __runtime_state_kind: "Set", value: ["n_old"] }
|
|
18
|
+
};
|
|
19
|
+
const server = http.createServer((req, res) => {
|
|
20
|
+
let body = "";
|
|
21
|
+
req.on("data", (chunk) => { body += chunk; });
|
|
22
|
+
req.on("end", () => {
|
|
23
|
+
if (req.method === "GET") {
|
|
24
|
+
calls.get += 1;
|
|
25
|
+
if (failReads) { res.writeHead(504, { "content-type": "application/json" }); res.end('{"message":"Gateway Timeout"}'); return; }
|
|
26
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
27
|
+
res.end(JSON.stringify([{ snapshot: durable, persisted_at_iso: "2026-10-17T00:00:00.000Z" }]));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
calls.post += 1;
|
|
31
|
+
calls.lastPostBody = JSON.parse(body);
|
|
32
|
+
calls.keys.push(calls.lastPostBody[0].state_key);
|
|
33
|
+
res.writeHead(201); res.end();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, calls, recover: () => { failReads = false; } })));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
const fake = await startFakeSupabase();
|
|
41
|
+
const store = createRuntimeStateStore({
|
|
42
|
+
supabaseUrl: `http://127.0.0.1:${fake.server.address().port}`,
|
|
43
|
+
supabaseServiceRoleKey: "test-key",
|
|
44
|
+
intervalMs: 1000
|
|
45
|
+
});
|
|
46
|
+
const state = { providerRegistry: new Map(), providerPayouts: [], acceptedPaymentNonces: new Set() };
|
|
47
|
+
try {
|
|
48
|
+
// 1. Boot restore fails (504) -> writes locked.
|
|
49
|
+
await assert.rejects(() => store.load(state), /504/, "boot restore surfaces the 504");
|
|
50
|
+
assert.strictEqual(store.restoreConfirmed(), false, "writes locked after a failed restore");
|
|
51
|
+
assert.strictEqual(store.inspect().writes_locked_until_restore, true, "inspect reports the lock");
|
|
52
|
+
|
|
53
|
+
// 2. The process keeps serving on empty state; a post-boot write lands in memory.
|
|
54
|
+
state.acceptedPaymentNonces.add("n_new");
|
|
55
|
+
state.providerPayouts.push({ payout_id: "provpay_2", status: "pending" });
|
|
56
|
+
store.markDirty();
|
|
57
|
+
assert.strictEqual(await store.flush(state), false, "flush refused while the restore is unconfirmed");
|
|
58
|
+
assert.strictEqual(fake.calls.post, 0, "NOTHING was written over the durable snapshot");
|
|
59
|
+
|
|
60
|
+
// 3. The interval retries the restore while the backend still fails: still no write.
|
|
61
|
+
let restoredCallback = false;
|
|
62
|
+
store.start(state, () => { restoredCallback = true; });
|
|
63
|
+
await new Promise((r) => setTimeout(r, 2300));
|
|
64
|
+
assert.ok(fake.calls.get >= 2, "restore retried in the background");
|
|
65
|
+
assert.strictEqual(fake.calls.post, 0, "still no write while the backend is down");
|
|
66
|
+
|
|
67
|
+
// 4. Backend recovers: restore merges the snapshot UNDER the post-boot changes, then writes.
|
|
68
|
+
fake.recover();
|
|
69
|
+
await new Promise((r) => setTimeout(r, 2600));
|
|
70
|
+
assert.strictEqual(restoredCallback, true, "onRestored fired");
|
|
71
|
+
assert.strictEqual(store.restoreConfirmed(), true, "writes unlocked after a successful restore");
|
|
72
|
+
assert.strictEqual(state.providerRegistry.get("mcp_executor").status, "active", "durable provider restored");
|
|
73
|
+
assert.ok(state.acceptedPaymentNonces.has("n_old") && state.acceptedPaymentNonces.has("n_new"), "nonce sets unioned");
|
|
74
|
+
assert.deepStrictEqual(state.providerPayouts.map((p) => p.payout_id), ["provpay_1", "provpay_2"], "ledger keeps durable + post-boot rows");
|
|
75
|
+
assert.ok(fake.calls.post >= 1, "the merged state is persisted once writes unlock");
|
|
76
|
+
const written = fake.calls.lastPostBody[0].snapshot;
|
|
77
|
+
assert.strictEqual(written.providerRegistry.value[0][0], "mcp_executor", "the write carries the restored provider (no clobber)");
|
|
78
|
+
|
|
79
|
+
// 5. Unchanged state is never rewritten (a read-only POST marks dirty but changes nothing).
|
|
80
|
+
const postsBefore = fake.calls.post;
|
|
81
|
+
store.markDirty();
|
|
82
|
+
assert.strictEqual(await store.flush(state), false, "unchanged snapshot skipped");
|
|
83
|
+
assert.strictEqual(fake.calls.post, postsBefore, "no write for an unchanged snapshot");
|
|
84
|
+
assert.ok(store.inspect().skipped_unchanged_writes >= 1, "skip counted");
|
|
85
|
+
state.acceptedPaymentNonces.add("n_newer");
|
|
86
|
+
store.markDirty();
|
|
87
|
+
assert.strictEqual(await store.flush(state), true, "a real change is written");
|
|
88
|
+
|
|
89
|
+
// 6. Rolling history copy: written alongside the main row, under a ::history:: key.
|
|
90
|
+
assert.ok(fake.calls.keys.some((key) => /^default::history::\d+$/.test(key)), "rolling history row written");
|
|
91
|
+
assert.ok(fake.calls.keys.includes("default"), "main row written");
|
|
92
|
+
assert.strictEqual(store.inspect().last_history_error, null, "history write succeeded");
|
|
93
|
+
await store.stop(state);
|
|
94
|
+
} finally {
|
|
95
|
+
fake.server.close();
|
|
96
|
+
}
|
|
97
|
+
// 7. Quote pruning: expired never-paid quotes older than the retention are dropped; executed and
|
|
98
|
+
// recent ones are kept.
|
|
99
|
+
const { pruneStaleQuotes } = require("../lib/commerce_quotes");
|
|
100
|
+
const day = 24 * 60 * 60 * 1000;
|
|
101
|
+
const now = Date.parse("2026-10-17T00:00:00.000Z");
|
|
102
|
+
const qs = { quotes: new Map([
|
|
103
|
+
["q_old_expired", { status: "expired", expires_at_iso: new Date(now - 10 * day).toISOString() }],
|
|
104
|
+
["q_recent_expired", { status: "expired", expires_at_iso: new Date(now - 2 * day).toISOString() }],
|
|
105
|
+
["q_old_executed", { status: "executed", transaction_id: "txn_1", expires_at_iso: new Date(now - 30 * day).toISOString() }],
|
|
106
|
+
["q_live", { status: "quoted", expires_at_iso: new Date(now + day).toISOString() }]
|
|
107
|
+
]) };
|
|
108
|
+
assert.strictEqual(pruneStaleQuotes(qs, now), 1, "exactly one stale quote pruned");
|
|
109
|
+
assert.deepStrictEqual(Array.from(qs.quotes.keys()).sort(), ["q_live", "q_old_executed", "q_recent_expired"], "executed / recent / live quotes kept");
|
|
110
|
+
|
|
111
|
+
console.log("xytara verify:runtime-state-guard passed");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main().catch((error) => {
|
|
115
|
+
console.error(error && error.message ? error.message : error);
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
});
|