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.
Files changed (68) hide show
  1. package/.env.example +38 -4
  2. package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
  3. package/REAL_PAYMENT_SETUP.md +14 -7
  4. package/RELEASE_NOTES.md +9 -4
  5. package/fixtures/catalog.js +22 -0
  6. package/lib/a2a_rpc.js +287 -280
  7. package/lib/account_auth.js +585 -585
  8. package/lib/agent_identity.js +162 -162
  9. package/lib/asyncapi_contract.js +3 -3
  10. package/lib/bolt_adapter.js +187 -187
  11. package/lib/bolt_payment_front.js +224 -224
  12. package/lib/buyer_event_log.js +215 -0
  13. package/lib/buyer_safety_policy.js +132 -0
  14. package/lib/checkout_processor_adapter.js +217 -217
  15. package/lib/command_flow.js +911 -722
  16. package/lib/commerce_artifacts.js +591 -591
  17. package/lib/commerce_bridge.js +274 -274
  18. package/lib/commerce_cases.js +399 -399
  19. package/lib/commerce_client.js +7694 -7694
  20. package/lib/commerce_quotes.js +29 -0
  21. package/lib/commerce_runtime.js +322 -322
  22. package/lib/execution_dedupe.js +86 -86
  23. package/lib/external_credit_bridge.js +142 -142
  24. package/lib/http_transport.js +99 -81
  25. package/lib/l402_adapter.js +241 -241
  26. package/lib/l402_payment_front.js +265 -265
  27. package/lib/mpp_adapter.js +237 -237
  28. package/lib/openapi_contract.js +4 -0
  29. package/lib/payment_config.js +257 -253
  30. package/lib/payment_verification.js +357 -357
  31. package/lib/provider_payout_execution.js +155 -155
  32. package/lib/provider_registry.js +852 -842
  33. package/lib/rate_limiter.js +81 -81
  34. package/lib/runtime_state_store.js +495 -369
  35. package/lib/settlement_bsv_live.js +600 -600
  36. package/lib/settlement_bsv_teranode.js +117 -117
  37. package/lib/stripe_mpp_payment_front.js +258 -258
  38. package/lib/x402_facilitator.js +198 -191
  39. package/lib/x402_lane_contract.js +5 -3
  40. package/lib/x402_settlement_recovery.js +175 -0
  41. package/package.json +9 -3
  42. package/scripts/buyer_events_daily_report.js +79 -0
  43. package/scripts/state_backup_offsite.js +196 -0
  44. package/scripts/verify_a2a_rpc.js +131 -105
  45. package/scripts/verify_agent_identity.js +153 -153
  46. package/scripts/verify_all.js +1 -1
  47. package/scripts/verify_bolt_adapter.js +128 -128
  48. package/scripts/verify_buyer_event_log.js +123 -0
  49. package/scripts/verify_buyer_safety.js +141 -0
  50. package/scripts/verify_cdp_facilitator.js +110 -110
  51. package/scripts/verify_checkout_processor.js +109 -109
  52. package/scripts/verify_commerce_bridge.js +177 -177
  53. package/scripts/verify_execution_dedupe.js +189 -189
  54. package/scripts/verify_execution_surfacing.js +76 -76
  55. package/scripts/verify_final_activation.js +191 -191
  56. package/scripts/verify_l402_adapter.js +86 -86
  57. package/scripts/verify_lifecycle_spend_binding.js +88 -0
  58. package/scripts/verify_mpp_adapter.js +128 -128
  59. package/scripts/verify_production_readiness.js +268 -268
  60. package/scripts/verify_rate_limiter.js +52 -52
  61. package/scripts/verify_refund_credit.js +106 -106
  62. package/scripts/verify_release_candidate.js +81 -81
  63. package/scripts/verify_runtime_state_guard.js +117 -0
  64. package/scripts/verify_service.js +15421 -15421
  65. package/scripts/verify_verify_execute_settle.js +209 -208
  66. package/scripts/verify_x402_facilitator.js +307 -286
  67. package/scripts/verify_x402_settlement_recovery.js +153 -0
  68. package/server.js +12242 -11974
@@ -1,162 +1,162 @@
1
- "use strict";
2
-
3
- // Cryptographic agent identity for delegated-spend credentials.
4
- //
5
- // A spend credential may be bound to an agent's ed25519 public key (the same
6
- // SPKI scheme xoonya's proof engine uses). When it is, presenting the bearer
7
- // token is no longer sufficient: the caller must also prove control of the
8
- // private key by signing a fresh challenge. This turns the credential from a
9
- // pure bearer secret into a key-bound, verifiable delegated authority.
10
- //
11
- // Backward-compatible: a credential with no bound key keeps the existing
12
- // bearer-only behavior. Dependency-free (node crypto; ed25519 verify is native).
13
-
14
- const crypto = require("crypto");
15
-
16
- // Accept an ed25519 public key as SPKI PEM or base64-encoded SPKI DER.
17
- // Returns { key: KeyObject, spki_der_b64, key_ref } or null.
18
- function normalizeAgentPublicKey(input) {
19
- if (typeof input !== "string" || !input.trim()) return null;
20
- const s = input.trim();
21
- let key = null;
22
- try {
23
- if (s.includes("BEGIN PUBLIC KEY")) {
24
- key = crypto.createPublicKey({ key: s, format: "pem" });
25
- } else {
26
- const der = Buffer.from(s.replace(/\s+/g, ""), "base64");
27
- if (der.length === 0) return null;
28
- key = crypto.createPublicKey({ key: der, format: "der", type: "spki" });
29
- }
30
- } catch (_) {
31
- return null;
32
- }
33
- if (!key || key.asymmetricKeyType !== "ed25519") return null;
34
- const spki = key.export({ type: "spki", format: "der" });
35
- const keyRef = "ed25519-spki-sha256:" + crypto.createHash("sha256").update(spki).digest("hex");
36
- return { key, spki_der_b64: spki.toString("base64"), key_ref: keyRef };
37
- }
38
-
39
- // The stable, public fingerprint of an agent key (matches xoonya's ref format).
40
- function deriveAgentKeyRef(input) {
41
- const m = normalizeAgentPublicKey(input);
42
- return m ? m.key_ref : null;
43
- }
44
-
45
- // A deterministic digest committing a spend proof to the specific request it authorizes
46
- // (account + units + task). A signature over a message carrying this digest can only be
47
- // used for that exact account/amount/task — closing the "fresh signature repurposed for a
48
- // different (within-cap) action" gap. Returns null when there is nothing to bind.
49
- function buildSpendBindingDigest(binding) {
50
- if (!binding || typeof binding !== "object") return null;
51
- const toStr = (v) => (v === undefined || v === null ? "" : String(v).trim());
52
- const account = toStr(binding.account_id);
53
- const units = toStr(binding.units);
54
- const task = toStr(binding.task_ref);
55
- if (!account && !units && !task) return null;
56
- // JSON array of fixed-order fields: unambiguous / injection-free (JSON escapes the field
57
- // values), unlike a delimiter-joined string where a value containing the delimiter could
58
- // collide two different (account, units, task) tuples onto the same digest.
59
- const canonical = JSON.stringify(["naxytra-spend-binding/v1", account, units, task]);
60
- return crypto.createHash("sha256").update(canonical, "utf8").digest("hex");
61
- }
62
-
63
- // The canonical challenge an agent signs to prove key control for a spend action. When a
64
- // bindingDigest is supplied the message additionally commits to the request (account/amount/
65
- // task); without it, the legacy unbound message (credential id + timestamp) is produced.
66
- function buildSpendAuthMessage(credentialId, timestamp, bindingDigest) {
67
- const base = `naxytra-spend-auth:${credentialId}:${timestamp}`;
68
- return bindingDigest ? `${base}:${bindingDigest}` : base;
69
- }
70
-
71
- // Verify an ed25519 signature (base64) over `message` against the bound key.
72
- function verifyAgentSignature(publicKeyInput, message, signatureB64) {
73
- const m = normalizeAgentPublicKey(publicKeyInput);
74
- if (!m) return false;
75
- let sig;
76
- try {
77
- sig = Buffer.from(String(signatureB64 || ""), "base64");
78
- } catch (_) {
79
- return false;
80
- }
81
- if (sig.length === 0) return false;
82
- try {
83
- return crypto.verify(null, Buffer.from(String(message), "utf8"), m.key, sig);
84
- } catch (_) {
85
- return false;
86
- }
87
- }
88
-
89
- // Verify a spend-auth proof for a credential that is key-bound.
90
- // proof = { signature, timestamp(ms) }. Returns
91
- // { ok:true, canonical, message } | { ok:false, reason }.
92
- // Freshness is bounded in BOTH directions (too old, or too far in the future).
93
- // `canonical` is the decoded-signature fingerprint callers MUST use as the
94
- // single-use replay key (so a re-encoded base64 of the same bytes cannot slip
95
- // past the guard).
96
- //
97
- // options = { maxPastSeconds=120, maxFutureSeconds=30, requestBinding, requireBinding }.
98
- // Freshness is bounded in BOTH directions. When options.requestBinding is supplied, a proof
99
- // that signed the BOUND message (credential + timestamp + request digest) verifies and is
100
- // reported as bound:true — that signature is usable ONLY for this exact account/amount/task,
101
- // so a captured fresh signature cannot be repurposed for a different (even within-cap)
102
- // request. An unbound signature still verifies (back-compat) UNLESS options.requireBinding is
103
- // set (strict mode), which refuses anything not request-bound. `canonical` is the decoded-
104
- // signature fingerprint callers MUST use as the single-use replay key.
105
- function verifySpendAuthProof(agentPublicKey, credentialId, proof, options = {}) {
106
- const opts = options && typeof options === "object" ? options : {};
107
- const maxPastSeconds = Number.isFinite(opts.maxPastSeconds) ? opts.maxPastSeconds : 120;
108
- const maxFutureSeconds = Number.isFinite(opts.maxFutureSeconds) ? opts.maxFutureSeconds : 30;
109
- const requireBinding = Boolean(opts.requireBinding);
110
- const bindingDigest = buildSpendBindingDigest(opts.requestBinding);
111
-
112
- const signature = proof && typeof proof.signature === "string" ? proof.signature.trim() : "";
113
- const timestampRaw = proof && (proof.timestamp !== undefined && proof.timestamp !== null) ? String(proof.timestamp).trim() : "";
114
- if (!signature || !timestampRaw) return { ok: false, reason: "agent_signature_required" };
115
- const timestamp = Number(timestampRaw);
116
- if (!Number.isFinite(timestamp)) return { ok: false, reason: "agent_signature_timestamp_invalid" };
117
- // Timestamp is milliseconds since epoch. Positive age = in the past.
118
- const ageSeconds = (Date.now() - timestamp) / 1000;
119
- if (ageSeconds > Math.max(1, maxPastSeconds)) return { ok: false, reason: "agent_signature_expired" };
120
- if (ageSeconds < -Math.max(0, maxFutureSeconds)) return { ok: false, reason: "agent_signature_future" };
121
- if (requireBinding && !bindingDigest) return { ok: false, reason: "spend_binding_required" };
122
-
123
- // Prefer the request-bound message; fall back to the unbound message only when binding is
124
- // not strictly required. A bound signature whose digest mismatches the actual request
125
- // verifies against neither message -> rejected.
126
- let message = null;
127
- let bound = false;
128
- if (bindingDigest) {
129
- const boundMessage = buildSpendAuthMessage(credentialId, timestampRaw, bindingDigest);
130
- if (verifyAgentSignature(agentPublicKey, boundMessage, signature)) {
131
- message = boundMessage;
132
- bound = true;
133
- }
134
- }
135
- if (!message && !requireBinding) {
136
- const unboundMessage = buildSpendAuthMessage(credentialId, timestampRaw);
137
- if (verifyAgentSignature(agentPublicKey, unboundMessage, signature)) {
138
- message = unboundMessage;
139
- bound = false;
140
- }
141
- }
142
- if (!message) {
143
- return { ok: false, reason: requireBinding ? "agent_signature_binding_required_or_invalid" : "agent_signature_invalid" };
144
- }
145
- // Canonical replay key = hex of the decoded signature bytes (encoding-agnostic).
146
- let canonical;
147
- try {
148
- canonical = Buffer.from(signature, "base64").toString("hex");
149
- } catch (_) {
150
- return { ok: false, reason: "agent_signature_invalid" };
151
- }
152
- return { ok: true, canonical, message, bound, expires_at_ms: timestamp + Math.max(1, maxPastSeconds) * 1000 };
153
- }
154
-
155
- module.exports = {
156
- normalizeAgentPublicKey,
157
- deriveAgentKeyRef,
158
- buildSpendAuthMessage,
159
- buildSpendBindingDigest,
160
- verifyAgentSignature,
161
- verifySpendAuthProof
162
- };
1
+ "use strict";
2
+
3
+ // Cryptographic agent identity for delegated-spend credentials.
4
+ //
5
+ // A spend credential may be bound to an agent's ed25519 public key (the same
6
+ // SPKI scheme xoonya's proof engine uses). When it is, presenting the bearer
7
+ // token is no longer sufficient: the caller must also prove control of the
8
+ // private key by signing a fresh challenge. This turns the credential from a
9
+ // pure bearer secret into a key-bound, verifiable delegated authority.
10
+ //
11
+ // Backward-compatible: a credential with no bound key keeps the existing
12
+ // bearer-only behavior. Dependency-free (node crypto; ed25519 verify is native).
13
+
14
+ const crypto = require("crypto");
15
+
16
+ // Accept an ed25519 public key as SPKI PEM or base64-encoded SPKI DER.
17
+ // Returns { key: KeyObject, spki_der_b64, key_ref } or null.
18
+ function normalizeAgentPublicKey(input) {
19
+ if (typeof input !== "string" || !input.trim()) return null;
20
+ const s = input.trim();
21
+ let key = null;
22
+ try {
23
+ if (s.includes("BEGIN PUBLIC KEY")) {
24
+ key = crypto.createPublicKey({ key: s, format: "pem" });
25
+ } else {
26
+ const der = Buffer.from(s.replace(/\s+/g, ""), "base64");
27
+ if (der.length === 0) return null;
28
+ key = crypto.createPublicKey({ key: der, format: "der", type: "spki" });
29
+ }
30
+ } catch (_) {
31
+ return null;
32
+ }
33
+ if (!key || key.asymmetricKeyType !== "ed25519") return null;
34
+ const spki = key.export({ type: "spki", format: "der" });
35
+ const keyRef = "ed25519-spki-sha256:" + crypto.createHash("sha256").update(spki).digest("hex");
36
+ return { key, spki_der_b64: spki.toString("base64"), key_ref: keyRef };
37
+ }
38
+
39
+ // The stable, public fingerprint of an agent key (matches xoonya's ref format).
40
+ function deriveAgentKeyRef(input) {
41
+ const m = normalizeAgentPublicKey(input);
42
+ return m ? m.key_ref : null;
43
+ }
44
+
45
+ // A deterministic digest committing a spend proof to the specific request it authorizes
46
+ // (account + units + task). A signature over a message carrying this digest can only be
47
+ // used for that exact account/amount/task — closing the "fresh signature repurposed for a
48
+ // different (within-cap) action" gap. Returns null when there is nothing to bind.
49
+ function buildSpendBindingDigest(binding) {
50
+ if (!binding || typeof binding !== "object") return null;
51
+ const toStr = (v) => (v === undefined || v === null ? "" : String(v).trim());
52
+ const account = toStr(binding.account_id);
53
+ const units = toStr(binding.units);
54
+ const task = toStr(binding.task_ref);
55
+ if (!account && !units && !task) return null;
56
+ // JSON array of fixed-order fields: unambiguous / injection-free (JSON escapes the field
57
+ // values), unlike a delimiter-joined string where a value containing the delimiter could
58
+ // collide two different (account, units, task) tuples onto the same digest.
59
+ const canonical = JSON.stringify(["naxytra-spend-binding/v1", account, units, task]);
60
+ return crypto.createHash("sha256").update(canonical, "utf8").digest("hex");
61
+ }
62
+
63
+ // The canonical challenge an agent signs to prove key control for a spend action. When a
64
+ // bindingDigest is supplied the message additionally commits to the request (account/amount/
65
+ // task); without it, the legacy unbound message (credential id + timestamp) is produced.
66
+ function buildSpendAuthMessage(credentialId, timestamp, bindingDigest) {
67
+ const base = `naxytra-spend-auth:${credentialId}:${timestamp}`;
68
+ return bindingDigest ? `${base}:${bindingDigest}` : base;
69
+ }
70
+
71
+ // Verify an ed25519 signature (base64) over `message` against the bound key.
72
+ function verifyAgentSignature(publicKeyInput, message, signatureB64) {
73
+ const m = normalizeAgentPublicKey(publicKeyInput);
74
+ if (!m) return false;
75
+ let sig;
76
+ try {
77
+ sig = Buffer.from(String(signatureB64 || ""), "base64");
78
+ } catch (_) {
79
+ return false;
80
+ }
81
+ if (sig.length === 0) return false;
82
+ try {
83
+ return crypto.verify(null, Buffer.from(String(message), "utf8"), m.key, sig);
84
+ } catch (_) {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ // Verify a spend-auth proof for a credential that is key-bound.
90
+ // proof = { signature, timestamp(ms) }. Returns
91
+ // { ok:true, canonical, message } | { ok:false, reason }.
92
+ // Freshness is bounded in BOTH directions (too old, or too far in the future).
93
+ // `canonical` is the decoded-signature fingerprint callers MUST use as the
94
+ // single-use replay key (so a re-encoded base64 of the same bytes cannot slip
95
+ // past the guard).
96
+ //
97
+ // options = { maxPastSeconds=120, maxFutureSeconds=30, requestBinding, requireBinding }.
98
+ // Freshness is bounded in BOTH directions. When options.requestBinding is supplied, a proof
99
+ // that signed the BOUND message (credential + timestamp + request digest) verifies and is
100
+ // reported as bound:true — that signature is usable ONLY for this exact account/amount/task,
101
+ // so a captured fresh signature cannot be repurposed for a different (even within-cap)
102
+ // request. An unbound signature still verifies (back-compat) UNLESS options.requireBinding is
103
+ // set (strict mode), which refuses anything not request-bound. `canonical` is the decoded-
104
+ // signature fingerprint callers MUST use as the single-use replay key.
105
+ function verifySpendAuthProof(agentPublicKey, credentialId, proof, options = {}) {
106
+ const opts = options && typeof options === "object" ? options : {};
107
+ const maxPastSeconds = Number.isFinite(opts.maxPastSeconds) ? opts.maxPastSeconds : 120;
108
+ const maxFutureSeconds = Number.isFinite(opts.maxFutureSeconds) ? opts.maxFutureSeconds : 30;
109
+ const requireBinding = Boolean(opts.requireBinding);
110
+ const bindingDigest = buildSpendBindingDigest(opts.requestBinding);
111
+
112
+ const signature = proof && typeof proof.signature === "string" ? proof.signature.trim() : "";
113
+ const timestampRaw = proof && (proof.timestamp !== undefined && proof.timestamp !== null) ? String(proof.timestamp).trim() : "";
114
+ if (!signature || !timestampRaw) return { ok: false, reason: "agent_signature_required" };
115
+ const timestamp = Number(timestampRaw);
116
+ if (!Number.isFinite(timestamp)) return { ok: false, reason: "agent_signature_timestamp_invalid" };
117
+ // Timestamp is milliseconds since epoch. Positive age = in the past.
118
+ const ageSeconds = (Date.now() - timestamp) / 1000;
119
+ if (ageSeconds > Math.max(1, maxPastSeconds)) return { ok: false, reason: "agent_signature_expired" };
120
+ if (ageSeconds < -Math.max(0, maxFutureSeconds)) return { ok: false, reason: "agent_signature_future" };
121
+ if (requireBinding && !bindingDigest) return { ok: false, reason: "spend_binding_required" };
122
+
123
+ // Prefer the request-bound message; fall back to the unbound message only when binding is
124
+ // not strictly required. A bound signature whose digest mismatches the actual request
125
+ // verifies against neither message -> rejected.
126
+ let message = null;
127
+ let bound = false;
128
+ if (bindingDigest) {
129
+ const boundMessage = buildSpendAuthMessage(credentialId, timestampRaw, bindingDigest);
130
+ if (verifyAgentSignature(agentPublicKey, boundMessage, signature)) {
131
+ message = boundMessage;
132
+ bound = true;
133
+ }
134
+ }
135
+ if (!message && !requireBinding) {
136
+ const unboundMessage = buildSpendAuthMessage(credentialId, timestampRaw);
137
+ if (verifyAgentSignature(agentPublicKey, unboundMessage, signature)) {
138
+ message = unboundMessage;
139
+ bound = false;
140
+ }
141
+ }
142
+ if (!message) {
143
+ return { ok: false, reason: requireBinding ? "agent_signature_binding_required_or_invalid" : "agent_signature_invalid" };
144
+ }
145
+ // Canonical replay key = hex of the decoded signature bytes (encoding-agnostic).
146
+ let canonical;
147
+ try {
148
+ canonical = Buffer.from(signature, "base64").toString("hex");
149
+ } catch (_) {
150
+ return { ok: false, reason: "agent_signature_invalid" };
151
+ }
152
+ return { ok: true, canonical, message, bound, expires_at_ms: timestamp + Math.max(1, maxPastSeconds) * 1000 };
153
+ }
154
+
155
+ module.exports = {
156
+ normalizeAgentPublicKey,
157
+ deriveAgentKeyRef,
158
+ buildSpendAuthMessage,
159
+ buildSpendBindingDigest,
160
+ verifyAgentSignature,
161
+ verifySpendAuthProof
162
+ };
@@ -13,9 +13,9 @@ function buildAsyncApiDocument() {
13
13
  },
14
14
  servers: {
15
15
  httpRuntime: {
16
- host: "127.0.0.1:4320",
17
- protocol: "http",
18
- description: "Local xytara HTTP event and handoff entrypoint"
16
+ host: String(process.env.XYTARA_PUBLIC_BASE_URL || "https://xytara.onrender.com").trim().replace(/^https?:\/\//, "").replace(/\/+$/, ""),
17
+ protocol: "https",
18
+ description: "Hosted xytara HTTP event and handoff entrypoint"
19
19
  }
20
20
  },
21
21
  channels: {