settld 0.2.3 → 0.2.5

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 (118) hide show
  1. package/docs/CONFIG.md +12 -0
  2. package/docs/README.md +3 -0
  3. package/docs/ops/HOSTED_BASELINE_R2.md +4 -2
  4. package/docs/ops/MINIMUM_PRODUCTION_TOPOLOGY.md +19 -7
  5. package/docs/ops/PRODUCTION_DEPLOYMENT_CHECKLIST.md +8 -3
  6. package/package.json +3 -1
  7. package/scripts/ci/run-public-onboarding-gate.mjs +136 -0
  8. package/scripts/setup/login.mjs +111 -17
  9. package/scripts/setup/onboard.mjs +176 -40
  10. package/scripts/setup/onboarding-failure-taxonomy.mjs +96 -0
  11. package/scripts/setup/onboarding-state-machine.mjs +102 -0
  12. package/services/magic-link/README.md +343 -0
  13. package/services/magic-link/assets/samples/closepack/known-bad/acceptance/acceptance_criteria.json +1 -0
  14. package/services/magic-link/assets/samples/closepack/known-bad/acceptance/acceptance_evaluation.json +1 -0
  15. package/services/magic-link/assets/samples/closepack/known-bad/attestation/bundle_head_attestation.json +1 -0
  16. package/services/magic-link/assets/samples/closepack/known-bad/evidence/evidence_index.json +1 -0
  17. package/services/magic-link/assets/samples/closepack/known-bad/governance/policy.json +1 -0
  18. package/services/magic-link/assets/samples/closepack/known-bad/governance/revocations.json +1 -0
  19. package/services/magic-link/assets/samples/closepack/known-bad/manifest.json +1 -0
  20. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/attestation/bundle_head_attestation.json +1 -0
  21. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/governance/policy.json +1 -0
  22. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/governance/revocations.json +1 -0
  23. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/invoice/invoice_claim.json +1 -0
  24. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/manifest.json +1 -0
  25. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/metering/metering_report.json +1 -0
  26. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/attestation/bundle_head_attestation.json +1 -0
  27. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/events/events.jsonl +1 -0
  28. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/events/payload_material.jsonl +1 -0
  29. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/global/events/events.jsonl +1 -0
  30. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/global/events/payload_material.jsonl +1 -0
  31. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/global/snapshot.json +1 -0
  32. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/policy.json +1 -0
  33. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/revocations.json +1 -0
  34. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/events/events.jsonl +0 -0
  35. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/events/payload_material.jsonl +0 -0
  36. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/snapshot.json +1 -0
  37. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/job/snapshot.json +1 -0
  38. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/keys/public_keys.json +1 -0
  39. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/manifest.json +1 -0
  40. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/verify/report.json +1 -0
  41. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/verify/verification_report.json +1 -0
  42. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/pricing/pricing_matrix.json +1 -0
  43. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/pricing/pricing_matrix_signatures.json +1 -0
  44. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/settld.json +1 -0
  45. package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/verify/verification_report.json +1 -0
  46. package/services/magic-link/assets/samples/closepack/known-bad/settld.json +1 -0
  47. package/services/magic-link/assets/samples/closepack/known-bad/sla/sla_definition.json +1 -0
  48. package/services/magic-link/assets/samples/closepack/known-bad/sla/sla_evaluation.json +1 -0
  49. package/services/magic-link/assets/samples/closepack/known-bad/verify/verification_report.json +1 -0
  50. package/services/magic-link/assets/samples/closepack/known-good/acceptance/acceptance_criteria.json +1 -0
  51. package/services/magic-link/assets/samples/closepack/known-good/acceptance/acceptance_evaluation.json +1 -0
  52. package/services/magic-link/assets/samples/closepack/known-good/attestation/bundle_head_attestation.json +1 -0
  53. package/services/magic-link/assets/samples/closepack/known-good/evidence/evidence_index.json +1 -0
  54. package/services/magic-link/assets/samples/closepack/known-good/governance/policy.json +1 -0
  55. package/services/magic-link/assets/samples/closepack/known-good/governance/revocations.json +1 -0
  56. package/services/magic-link/assets/samples/closepack/known-good/manifest.json +1 -0
  57. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/attestation/bundle_head_attestation.json +1 -0
  58. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/governance/policy.json +1 -0
  59. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/governance/revocations.json +1 -0
  60. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/invoice/invoice_claim.json +1 -0
  61. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/manifest.json +1 -0
  62. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/metering/metering_report.json +1 -0
  63. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/attestation/bundle_head_attestation.json +1 -0
  64. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/events/events.jsonl +1 -0
  65. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/events/payload_material.jsonl +1 -0
  66. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/global/events/events.jsonl +1 -0
  67. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/global/events/payload_material.jsonl +1 -0
  68. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/global/snapshot.json +1 -0
  69. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/policy.json +1 -0
  70. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/revocations.json +1 -0
  71. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/events/events.jsonl +0 -0
  72. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/events/payload_material.jsonl +0 -0
  73. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/snapshot.json +1 -0
  74. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/job/snapshot.json +1 -0
  75. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/keys/public_keys.json +1 -0
  76. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/manifest.json +1 -0
  77. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/verify/report.json +1 -0
  78. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/verify/verification_report.json +1 -0
  79. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/pricing/pricing_matrix.json +1 -0
  80. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/pricing/pricing_matrix_signatures.json +1 -0
  81. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/settld.json +1 -0
  82. package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/verify/verification_report.json +1 -0
  83. package/services/magic-link/assets/samples/closepack/known-good/settld.json +1 -0
  84. package/services/magic-link/assets/samples/closepack/known-good/sla/sla_definition.json +1 -0
  85. package/services/magic-link/assets/samples/closepack/known-good/sla/sla_evaluation.json +1 -0
  86. package/services/magic-link/assets/samples/closepack/known-good/verify/verification_report.json +1 -0
  87. package/services/magic-link/assets/samples/trust.json +11 -0
  88. package/services/magic-link/src/audit-log.js +24 -0
  89. package/services/magic-link/src/buyer-auth.js +220 -0
  90. package/services/magic-link/src/buyer-notifications.js +402 -0
  91. package/services/magic-link/src/buyer-users.js +129 -0
  92. package/services/magic-link/src/decision-otp.js +156 -0
  93. package/services/magic-link/src/decisions.js +92 -0
  94. package/services/magic-link/src/ingest-keys.js +137 -0
  95. package/services/magic-link/src/maintenance.js +70 -0
  96. package/services/magic-link/src/onboarding-email-sequence.js +331 -0
  97. package/services/magic-link/src/payment-triggers.js +733 -0
  98. package/services/magic-link/src/pdf.js +149 -0
  99. package/services/magic-link/src/policy.js +69 -0
  100. package/services/magic-link/src/redaction.js +6 -0
  101. package/services/magic-link/src/render-model.js +70 -0
  102. package/services/magic-link/src/retention-gc.js +158 -0
  103. package/services/magic-link/src/run-records.js +496 -0
  104. package/services/magic-link/src/s3.js +171 -0
  105. package/services/magic-link/src/server.js +15788 -0
  106. package/services/magic-link/src/settlement-decisions.js +84 -0
  107. package/services/magic-link/src/smtp.js +202 -0
  108. package/services/magic-link/src/storage-cli.js +88 -0
  109. package/services/magic-link/src/storage-format.js +59 -0
  110. package/services/magic-link/src/tenant-billing.js +115 -0
  111. package/services/magic-link/src/tenant-onboarding.js +467 -0
  112. package/services/magic-link/src/tenant-settings.js +1140 -0
  113. package/services/magic-link/src/usage.js +80 -0
  114. package/services/magic-link/src/verify-queue.js +179 -0
  115. package/services/magic-link/src/verify-worker.js +157 -0
  116. package/services/magic-link/src/webhook-retries.js +542 -0
  117. package/services/magic-link/src/webhooks.js +218 -0
  118. package/src/api/app.js +129 -0
@@ -0,0 +1,218 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import http from "node:http";
5
+ import https from "node:https";
6
+
7
+ import { decryptWebhookSecret } from "./tenant-settings.js";
8
+
9
+ function isPlainObject(v) {
10
+ return Boolean(v && typeof v === "object" && !Array.isArray(v) && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null));
11
+ }
12
+
13
+ function hmacSha256Hex(secret, message) {
14
+ return crypto.createHmac("sha256", String(secret ?? "")).update(String(message ?? ""), "utf8").digest("hex");
15
+ }
16
+
17
+ function isHttpSuccessStatus(statusCode) {
18
+ const code = Number(statusCode);
19
+ return Number.isFinite(code) && code >= 200 && code < 300;
20
+ }
21
+
22
+ async function waitMs(ms) {
23
+ const n = Number(ms);
24
+ if (!Number.isFinite(n) || n <= 0) return;
25
+ await new Promise((resolve) => setTimeout(resolve, n));
26
+ }
27
+
28
+ function buildSignatureHeader({ secret, timestamp, body }) {
29
+ // Simple, stable scheme: v1 = HMAC_SHA256(secret, `${timestamp}.${body}`)
30
+ const ts = String(timestamp ?? "");
31
+ const msg = `${ts}.${String(body ?? "")}`;
32
+ const sig = hmacSha256Hex(secret, msg);
33
+ return { timestamp: ts, signature: `v1=${sig}` };
34
+ }
35
+
36
+ async function request({ url, method, headers, body, timeoutMs }) {
37
+ const u = new URL(url);
38
+ const lib = u.protocol === "https:" ? https : http;
39
+ return await new Promise((resolve) => {
40
+ const req = lib.request(
41
+ {
42
+ protocol: u.protocol,
43
+ hostname: u.hostname,
44
+ port: u.port ? Number(u.port) : u.protocol === "https:" ? 443 : 80,
45
+ path: u.pathname + u.search,
46
+ method,
47
+ headers,
48
+ timeout: timeoutMs
49
+ },
50
+ (res) => {
51
+ const chunks = [];
52
+ res.on("data", (d) => chunks.push(d));
53
+ res.on("end", () => {
54
+ resolve({ ok: true, statusCode: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") });
55
+ });
56
+ }
57
+ );
58
+ req.on("timeout", () => {
59
+ try { req.destroy(new Error("timeout")); } catch { /* ignore */ }
60
+ });
61
+ req.on("error", (err) => resolve({ ok: false, error: err?.message ?? String(err ?? "error") }));
62
+ req.end(body);
63
+ });
64
+ }
65
+
66
+ export function buildWebhookPayload({ event, tenantId, token, zipSha256, zipBytes, modeResolved, modeRequested, cliOut, publicBaseUrl, decisionReport = null, publicSummary = null, closePackZipUrl = null }) {
67
+ const base = publicBaseUrl ? String(publicBaseUrl).replace(/\/+$/, "") : "";
68
+ const rel = `/r/${token}`;
69
+ const url = base ? `${base}${rel}` : rel;
70
+
71
+ const errorCodes = Array.isArray(cliOut?.errors) ? cliOut.errors.map((e) => String(e?.code ?? "")).filter(Boolean) : [];
72
+ const warningCodes = Array.isArray(cliOut?.warnings) ? cliOut.warnings.map((w) => String(w?.code ?? "")).filter(Boolean) : [];
73
+
74
+ const payload = {
75
+ schemaVersion: "MagicLinkWebhookPayload.v1",
76
+ event: String(event ?? ""),
77
+ sentAt: new Date().toISOString(),
78
+ tenantId,
79
+ token,
80
+ magicLinkUrl: url,
81
+ zipSha256,
82
+ zipBytes,
83
+ modeRequested,
84
+ modeResolved,
85
+ verification: {
86
+ ok: Boolean(cliOut?.ok),
87
+ verificationOk: Boolean(cliOut?.verificationOk),
88
+ errorCodes,
89
+ warningCodes
90
+ },
91
+ artifacts: {
92
+ verifyJsonUrl: base ? `${base}${rel}/verify.json` : `${rel}/verify.json`,
93
+ bundleZipUrl: base ? `${base}${rel}/bundle.zip` : `${rel}/bundle.zip`,
94
+ receiptJsonUrl: base ? `${base}${rel}/receipt.json` : `${rel}/receipt.json`,
95
+ auditPacketUrl: base ? `${base}${rel}/audit-packet.zip` : `${rel}/audit-packet.zip`
96
+ }
97
+ };
98
+ if (closePackZipUrl) payload.artifacts.closePackZipUrl = base && String(closePackZipUrl).startsWith("/") ? `${base}${closePackZipUrl}` : closePackZipUrl;
99
+ if (decisionReport && typeof decisionReport === "object" && !Array.isArray(decisionReport)) {
100
+ payload.decision = {
101
+ decision: typeof decisionReport.decision === "string" ? decisionReport.decision : null,
102
+ decidedAt: typeof decisionReport.decidedAt === "string" ? decisionReport.decidedAt : null,
103
+ signerKeyId: typeof decisionReport.signerKeyId === "string" ? decisionReport.signerKeyId : null,
104
+ actorEmail: typeof decisionReport?.actor?.email === "string" ? decisionReport.actor.email : null
105
+ };
106
+ payload.artifacts.decisionReportUrl = base ? `${base}${rel}/settlement_decision_report.json` : `${rel}/settlement_decision_report.json`;
107
+ }
108
+ if (publicSummary && typeof publicSummary === "object" && !Array.isArray(publicSummary)) {
109
+ payload.invoice = publicSummary.invoiceClaim && typeof publicSummary.invoiceClaim === "object" && !Array.isArray(publicSummary.invoiceClaim)
110
+ ? {
111
+ invoiceId: typeof publicSummary.invoiceClaim.invoiceId === "string" ? publicSummary.invoiceClaim.invoiceId : null,
112
+ currency: typeof publicSummary.invoiceClaim.currency === "string" ? publicSummary.invoiceClaim.currency : null,
113
+ totalCents: typeof publicSummary.invoiceClaim.totalCents === "string" ? publicSummary.invoiceClaim.totalCents : null
114
+ }
115
+ : null;
116
+ if (publicSummary.closePackSummaryV1 && typeof publicSummary.closePackSummaryV1 === "object" && !Array.isArray(publicSummary.closePackSummaryV1)) {
117
+ payload.closePack = publicSummary.closePackSummaryV1;
118
+ }
119
+ }
120
+ return payload;
121
+ }
122
+
123
+ export async function deliverTenantWebhooks({
124
+ dataDir,
125
+ tenantId,
126
+ token,
127
+ event,
128
+ payload,
129
+ webhooks,
130
+ settingsKey,
131
+ deliveryMode = "http",
132
+ timeoutMs = 5_000,
133
+ maxAttempts = 1,
134
+ retryBackoffMs = 0
135
+ }) {
136
+ const list = Array.isArray(webhooks) ? webhooks : [];
137
+ const body = JSON.stringify(payload ?? {});
138
+ const maxAttemptsSafe = Number.isInteger(maxAttempts) && maxAttempts > 0 ? maxAttempts : 1;
139
+ const retryBackoffSafe = Number.isInteger(retryBackoffMs) && retryBackoffMs >= 0 ? retryBackoffMs : 0;
140
+
141
+ const results = [];
142
+ for (let i = 0; i < list.length; i += 1) {
143
+ const w = list[i];
144
+ if (!isPlainObject(w)) continue;
145
+ if (!w.enabled) continue;
146
+ const events = Array.isArray(w.events) ? w.events.map(String) : [];
147
+ if (!events.includes(event)) continue;
148
+ const url = typeof w.url === "string" ? w.url.trim() : "";
149
+ if (!url) continue;
150
+
151
+ const secret = decryptWebhookSecret({ settingsKey, storedSecret: w.secret });
152
+ if (!secret) {
153
+ results.push({ ok: false, url, error: "WEBHOOK_SECRET_MISSING" });
154
+ continue;
155
+ }
156
+
157
+ const ts = new Date().toISOString();
158
+ const sig = buildSignatureHeader({ secret, timestamp: ts, body });
159
+ const headers = {
160
+ "content-type": "application/json; charset=utf-8",
161
+ "content-length": String(Buffer.byteLength(body, "utf8")),
162
+ "user-agent": "settld-magic-link/0",
163
+ "x-settld-event": String(event),
164
+ "x-settld-timestamp": sig.timestamp,
165
+ "x-settld-signature": sig.signature
166
+ };
167
+
168
+ const attempt = {
169
+ schemaVersion: "MagicLinkWebhookAttempt.v1",
170
+ tenantId,
171
+ token,
172
+ event,
173
+ url,
174
+ headers,
175
+ bodySha256: crypto.createHash("sha256").update(body, "utf8").digest("hex"),
176
+ sentAt: ts,
177
+ deliveryMode
178
+ };
179
+
180
+ const outDir = path.join(dataDir, "webhooks", deliveryMode === "record" ? "record" : "attempts");
181
+ await fs.mkdir(outDir, { recursive: true });
182
+ if (deliveryMode === "record") {
183
+ const id = `${token}_${Date.now()}_${i}`;
184
+ const fp = path.join(outDir, `${id}.json`);
185
+ await fs.writeFile(fp, JSON.stringify({ ...attempt, body, attempt: 1, maxAttempts: 1 }, null, 2) + "\n", "utf8");
186
+ results.push({ ok: true, url, recorded: true, attempts: 1 });
187
+ continue;
188
+ }
189
+
190
+ let finalResult = { ok: false, error: "request failed", statusCode: null };
191
+ let attemptsUsed = 0;
192
+ for (let attemptIndex = 1; attemptIndex <= maxAttemptsSafe; attemptIndex += 1) {
193
+ const id = `${token}_${Date.now()}_${i}_${attemptIndex}`;
194
+ const fp = path.join(outDir, `${id}.json`);
195
+
196
+ const res = await request({ url, method: "POST", headers, body, timeoutMs });
197
+ const delivered = Boolean(res.ok) && isHttpSuccessStatus(res.statusCode);
198
+ finalResult = delivered
199
+ ? { ok: true, statusCode: res.statusCode ?? 200, error: null }
200
+ : {
201
+ ok: false,
202
+ statusCode: Number.isFinite(Number(res.statusCode)) ? Number(res.statusCode) : null,
203
+ error: res.ok ? `HTTP_${res.statusCode ?? "UNKNOWN"}` : res.error ?? "request failed"
204
+ };
205
+ attemptsUsed = attemptIndex;
206
+ await fs.writeFile(fp, JSON.stringify({ ...attempt, attempt: attemptIndex, maxAttempts: maxAttemptsSafe, result: finalResult }, null, 2) + "\n", "utf8");
207
+
208
+ if (finalResult.ok) break;
209
+ if (attemptIndex < maxAttemptsSafe) {
210
+ const waitFor = retryBackoffSafe * (2 ** (attemptIndex - 1));
211
+ // Exponential backoff for transient webhook failures.
212
+ await waitMs(waitFor);
213
+ }
214
+ }
215
+ results.push({ url, ...finalResult, attempts: attemptsUsed });
216
+ }
217
+ return results;
218
+ }
package/src/api/app.js CHANGED
@@ -498,6 +498,12 @@ export function createApi({
498
498
  }
499
499
  const rateBucketsByApiKey = new Map(); // `${tenantId}\n${apiKeyId}` -> { tokens, lastMs }
500
500
  const ratePerKeyRefillPerMs = rateLimitPerKeyRpmValue ? rateLimitPerKeyRpmValue / 60_000 : 0;
501
+ const onboardingProxyBaseUrlRaw =
502
+ typeof process !== "undefined" ? process.env.PROXY_ONBOARDING_BASE_URL ?? process.env.PROXY_MAGIC_LINK_BASE_URL ?? null : null;
503
+ const onboardingProxyBaseUrl =
504
+ onboardingProxyBaseUrlRaw && String(onboardingProxyBaseUrlRaw).trim() !== ""
505
+ ? normalizeOptionalAbsoluteUrl(String(onboardingProxyBaseUrlRaw).trim(), { fieldName: "PROXY_ONBOARDING_BASE_URL" })?.replace(/\/+$/, "")
506
+ : null;
501
507
 
502
508
  function setProtocolResponseHeaders(res) {
503
509
  try {
@@ -21977,6 +21983,126 @@ export function createApi({
21977
21983
  return "info";
21978
21984
  }
21979
21985
 
21986
+ const ONBOARDING_PROXY_ROUTES = Object.freeze([
21987
+ { method: "GET", re: /^\/v1\/public\/auth-mode$/ },
21988
+ { method: "POST", re: /^\/v1\/public\/signup$/ },
21989
+ { method: "GET", re: /^\/v1\/buyer\/me$/ },
21990
+ { method: "POST", re: /^\/v1\/buyer\/logout$/ },
21991
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/buyer\/login\/otp$/ },
21992
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/buyer\/login$/ },
21993
+ { method: "GET", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding$/ },
21994
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding\/events$/ },
21995
+ { method: "GET", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding-metrics$/ },
21996
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding\/wallet-bootstrap$/ },
21997
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding\/wallet-funding$/ },
21998
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding\/runtime-bootstrap$/ },
21999
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding\/runtime-bootstrap\/smoke-test$/ },
22000
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding\/first-paid-call$/ },
22001
+ { method: "GET", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding\/first-paid-call\/history$/ },
22002
+ { method: "POST", re: /^\/v1\/tenants\/[a-zA-Z0-9_-]{1,64}\/onboarding\/conformance-matrix$/ }
22003
+ ]);
22004
+ const ONBOARDING_PROXY_BODY_METHODS = new Set(["POST", "PUT", "PATCH"]);
22005
+ const HOP_BY_HOP_HEADERS = new Set([
22006
+ "connection",
22007
+ "keep-alive",
22008
+ "proxy-authenticate",
22009
+ "proxy-authorization",
22010
+ "te",
22011
+ "trailer",
22012
+ "transfer-encoding",
22013
+ "upgrade",
22014
+ "host",
22015
+ "content-length"
22016
+ ]);
22017
+
22018
+ function isOnboardingProxyRoute({ method, path }) {
22019
+ const m = String(method ?? "").toUpperCase();
22020
+ const p = String(path ?? "");
22021
+ for (const route of ONBOARDING_PROXY_ROUTES) {
22022
+ if (route.method !== m) continue;
22023
+ if (route.re.test(p)) return true;
22024
+ }
22025
+ return false;
22026
+ }
22027
+
22028
+ async function proxyOnboardingRequest(req, res, { path, search, requestId }) {
22029
+ if (!onboardingProxyBaseUrl) {
22030
+ return sendError(
22031
+ res,
22032
+ 503,
22033
+ "onboarding proxy is not configured on this API host",
22034
+ { env: "PROXY_ONBOARDING_BASE_URL" },
22035
+ { code: "ONBOARDING_PROXY_NOT_CONFIGURED" }
22036
+ );
22037
+ }
22038
+ const upstreamFetch = typeof fetchFn === "function" ? fetchFn : globalThis.fetch;
22039
+ if (typeof upstreamFetch !== "function") {
22040
+ return sendError(res, 500, "onboarding proxy fetch is unavailable", null, { code: "ONBOARDING_PROXY_FETCH_UNAVAILABLE" });
22041
+ }
22042
+
22043
+ const targetUrl = new URL(`${path}${search || ""}`, `${onboardingProxyBaseUrl}/`).toString();
22044
+ const upstreamHeaders = new Headers();
22045
+ for (const [nameRaw, valueRaw] of Object.entries(req.headers ?? {})) {
22046
+ const name = String(nameRaw ?? "").toLowerCase();
22047
+ if (!name || HOP_BY_HOP_HEADERS.has(name)) continue;
22048
+ if (Array.isArray(valueRaw)) {
22049
+ if (!valueRaw.length) continue;
22050
+ upstreamHeaders.set(name, valueRaw.join(", "));
22051
+ continue;
22052
+ }
22053
+ if (valueRaw === undefined || valueRaw === null) continue;
22054
+ upstreamHeaders.set(name, String(valueRaw));
22055
+ }
22056
+ if (!upstreamHeaders.has("x-request-id") && requestId) {
22057
+ upstreamHeaders.set("x-request-id", String(requestId));
22058
+ }
22059
+
22060
+ let body = undefined;
22061
+ const method = String(req.method ?? "").toUpperCase();
22062
+ if (ONBOARDING_PROXY_BODY_METHODS.has(method)) {
22063
+ const raw = await readRawBody(req);
22064
+ if (raw && raw.length > 0) body = raw;
22065
+ }
22066
+
22067
+ let upstreamRes;
22068
+ try {
22069
+ upstreamRes = await upstreamFetch(targetUrl, {
22070
+ method,
22071
+ headers: upstreamHeaders,
22072
+ body
22073
+ });
22074
+ } catch (err) {
22075
+ return sendError(
22076
+ res,
22077
+ 502,
22078
+ "onboarding proxy upstream unreachable",
22079
+ { message: err?.message ?? String(err) },
22080
+ { code: "ONBOARDING_PROXY_UNREACHABLE" }
22081
+ );
22082
+ }
22083
+
22084
+ const bytes = Buffer.from(await upstreamRes.arrayBuffer());
22085
+ res.statusCode = upstreamRes.status;
22086
+ for (const [name, value] of upstreamRes.headers.entries()) {
22087
+ const n = String(name ?? "").toLowerCase();
22088
+ if (!n || HOP_BY_HOP_HEADERS.has(n)) continue;
22089
+ try {
22090
+ res.setHeader(n, value);
22091
+ } catch {
22092
+ // ignore invalid header copy
22093
+ }
22094
+ }
22095
+ try {
22096
+ const setCookies = typeof upstreamRes.headers.getSetCookie === "function" ? upstreamRes.headers.getSetCookie() : [];
22097
+ if (Array.isArray(setCookies) && setCookies.length > 0) {
22098
+ res.setHeader("set-cookie", setCookies);
22099
+ }
22100
+ } catch {
22101
+ // ignore
22102
+ }
22103
+ return res.end(bytes);
22104
+ }
22105
+
21980
22106
  async function handle(req, res) {
21981
22107
  const url = new URL(req.url ?? "/", "http://localhost");
21982
22108
  const path = url.pathname;
@@ -21990,6 +22116,9 @@ export function createApi({
21990
22116
  setProtocolResponseHeaders(res);
21991
22117
 
21992
22118
  return withLogContext({ requestId, route, method: req.method, path }, async () => {
22119
+ if (isOnboardingProxyRoute({ method: req.method, path })) {
22120
+ return proxyOnboardingRequest(req, res, { path, search: url.search, requestId });
22121
+ }
21993
22122
  const startedMs = Date.now();
21994
22123
  let tenantId = "tenant_default";
21995
22124
  let principalId = "anon";