@nodii/onboarding 0.0.1 → 0.1.0

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 (73) hide show
  1. package/README.md +75 -2
  2. package/dist/chunk-7NS7ENJV.js +232 -0
  3. package/dist/chunk-7NS7ENJV.js.map +1 -0
  4. package/dist/chunk-AZE3NPSY.js +186 -0
  5. package/dist/chunk-AZE3NPSY.js.map +1 -0
  6. package/dist/chunk-BMQINH6O.js +401 -0
  7. package/dist/chunk-BMQINH6O.js.map +1 -0
  8. package/dist/chunk-F75ZJERM.js +199 -0
  9. package/dist/chunk-F75ZJERM.js.map +1 -0
  10. package/dist/chunk-FIRSA7EH.js +3 -0
  11. package/dist/chunk-FIRSA7EH.js.map +1 -0
  12. package/dist/chunk-IPAKPLIF.js +1660 -0
  13. package/dist/chunk-IPAKPLIF.js.map +1 -0
  14. package/dist/chunk-JFDQZ3AC.js +3 -0
  15. package/dist/chunk-JFDQZ3AC.js.map +1 -0
  16. package/dist/chunk-JFIONONR.js +3 -0
  17. package/dist/chunk-JFIONONR.js.map +1 -0
  18. package/dist/chunk-KQTS47FC.js +546 -0
  19. package/dist/chunk-KQTS47FC.js.map +1 -0
  20. package/dist/chunk-LVJM4CTG.js +306 -0
  21. package/dist/chunk-LVJM4CTG.js.map +1 -0
  22. package/dist/chunk-SEKNVHD3.js +59 -0
  23. package/dist/chunk-SEKNVHD3.js.map +1 -0
  24. package/dist/chunk-SIMLMQPT.js +37 -0
  25. package/dist/chunk-SIMLMQPT.js.map +1 -0
  26. package/dist/chunk-U56GQITG.js +79 -0
  27. package/dist/chunk-U56GQITG.js.map +1 -0
  28. package/dist/chunk-U6AXKGYQ.js +101 -0
  29. package/dist/chunk-U6AXKGYQ.js.map +1 -0
  30. package/dist/chunk-VH6FWDWI.js +3 -0
  31. package/dist/chunk-VH6FWDWI.js.map +1 -0
  32. package/dist/chunk-XJTJPBJB.js +191 -0
  33. package/dist/chunk-XJTJPBJB.js.map +1 -0
  34. package/dist/chunk-Z4PA6C2Z.js +3 -0
  35. package/dist/chunk-Z4PA6C2Z.js.map +1 -0
  36. package/dist/client-CqE2dF9A.d.ts +86 -0
  37. package/dist/consent/index.d.ts +59 -0
  38. package/dist/consent/index.js +7 -0
  39. package/dist/consent/index.js.map +1 -0
  40. package/dist/controller-BQb62D0N.d.ts +177 -0
  41. package/dist/dynamic/index.d.ts +235 -0
  42. package/dist/dynamic/index.js +6 -0
  43. package/dist/dynamic/index.js.map +1 -0
  44. package/dist/errors-vJkye4a0.d.ts +110 -0
  45. package/dist/form/index.d.ts +64 -0
  46. package/dist/form/index.js +5 -0
  47. package/dist/form/index.js.map +1 -0
  48. package/dist/index-Dqfa890U.d.ts +83 -0
  49. package/dist/index.d.ts +16 -2
  50. package/dist/index.js +18 -2
  51. package/dist/index.js.map +1 -0
  52. package/dist/model-ESLSEbi0.d.ts +640 -0
  53. package/dist/model-mnjAEtKe.d.ts +253 -0
  54. package/dist/recaptcha/index.d.ts +102 -0
  55. package/dist/recaptcha/index.js +4 -0
  56. package/dist/recaptcha/index.js.map +1 -0
  57. package/dist/render-DC7P2BGQ.js +5 -0
  58. package/dist/render-DC7P2BGQ.js.map +1 -0
  59. package/dist/schema-BrXb9gnh.d.ts +267 -0
  60. package/dist/signup-client-BL5DfarJ.d.ts +392 -0
  61. package/dist/spec/index.d.ts +17 -0
  62. package/dist/spec/index.js +5 -0
  63. package/dist/spec/index.js.map +1 -0
  64. package/dist/submit/index.d.ts +42 -0
  65. package/dist/submit/index.js +6 -0
  66. package/dist/submit/index.js.map +1 -0
  67. package/dist/viewer/index.d.ts +4 -0
  68. package/dist/viewer/index.js +5 -0
  69. package/dist/viewer/index.js.map +1 -0
  70. package/dist/widget/index.d.ts +262 -0
  71. package/dist/widget/index.js +12 -0
  72. package/dist/widget/index.js.map +1 -0
  73. package/package.json +96 -6
@@ -0,0 +1,401 @@
1
+ import { validateLeadsPayload } from './chunk-SIMLMQPT.js';
2
+ import { DEFAULT_REQUEST_TIMEOUT_MS, OnboardingValidationError, fetchWithTimeout, OnboardingNetworkError, OnboardingBotChallengeError, OnboardingConflictError, OnboardingRateLimitError, OnboardingServerError, OnboardingConsentIncompleteError, OnboardingSpecStaleError, OnboardingConsentStaleError, OnboardingSubdomainTakenError } from './chunk-XJTJPBJB.js';
3
+ import { z } from 'zod/v3';
4
+
5
+ // src/submit/client.ts
6
+ var LEADS_PATH = "/api/v1/public/onboarding/leads";
7
+ var MAX_ATTEMPTS = 3;
8
+ var RETRY_DELAYS_MS = [1e3, 2e3];
9
+ async function resolveBotTokenGuarded(source) {
10
+ try {
11
+ return await resolveBotToken(source);
12
+ } catch (err) {
13
+ throw new OnboardingBotChallengeError(
14
+ err instanceof Error ? `Could not obtain a bot-challenge token: ${err.message}` : "Could not obtain a bot-challenge token."
15
+ );
16
+ }
17
+ }
18
+ async function resolveBotToken(source) {
19
+ return typeof source === "function" ? source() : source;
20
+ }
21
+ function generateIdempotencyKey() {
22
+ return globalThis.crypto.randomUUID();
23
+ }
24
+ function buildUrl(baseUrl, path) {
25
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
26
+ return `${base}${path}`;
27
+ }
28
+ function parseRetryAfter(response) {
29
+ const header = response.headers.get("retry-after");
30
+ if (!header) return null;
31
+ const seconds = Number.parseInt(header, 10);
32
+ return Number.isNaN(seconds) ? null : seconds;
33
+ }
34
+ async function sleep(ms) {
35
+ return new Promise((resolve) => setTimeout(resolve, ms));
36
+ }
37
+ function computePayloadFingerprint(values) {
38
+ const canonical = JSON.stringify([
39
+ values.companyName,
40
+ values.contactName,
41
+ values.email,
42
+ values.phone ?? null,
43
+ values.message ?? null,
44
+ [...values.interestModules ?? []].sort(),
45
+ values.sizeBand ?? null,
46
+ values.attribution ?? null,
47
+ values.privacyNoticeAcknowledged,
48
+ values.marketingOptIn,
49
+ values.intakeKind
50
+ ]);
51
+ let hash = 2166136261;
52
+ for (let i = 0; i < canonical.length; i++) {
53
+ hash ^= canonical.charCodeAt(i);
54
+ hash = Math.imul(hash, 16777619);
55
+ }
56
+ return (hash >>> 0).toString(16).padStart(8, "0");
57
+ }
58
+ async function parseSuccessBody(response) {
59
+ let text;
60
+ try {
61
+ text = await response.text();
62
+ } catch {
63
+ return { accepted: true };
64
+ }
65
+ if (text.trim() === "") {
66
+ return { accepted: true };
67
+ }
68
+ let json;
69
+ try {
70
+ json = JSON.parse(text);
71
+ } catch {
72
+ return { accepted: true };
73
+ }
74
+ if (typeof json === "object" && json !== null && json.accepted === true) {
75
+ return { accepted: true };
76
+ }
77
+ throw new OnboardingServerError(
78
+ response.status,
79
+ `Success status ${response.status} but response body did not confirm acceptance.`
80
+ );
81
+ }
82
+ async function submitLead(values, options) {
83
+ const fetchImpl = options.fetch ?? globalThis.fetch;
84
+ const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
85
+ const url = buildUrl(options.baseUrl, LEADS_PATH);
86
+ const base = { ...values };
87
+ if (base["phone"] === "" || base["phone"] === void 0) {
88
+ delete base["phone"];
89
+ }
90
+ if (base["message"] === "" || base["message"] === void 0) {
91
+ delete base["message"];
92
+ }
93
+ const headers = {
94
+ "Content-Type": "application/json",
95
+ "Idempotency-Key": options.idempotencyKey
96
+ };
97
+ let lastError = null;
98
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
99
+ options.onAttempt?.(attempt);
100
+ const botToken = options.botToken === void 0 ? void 0 : await resolveBotTokenGuarded(options.botToken);
101
+ const parsed = validateLeadsPayload({ ...base, botToken });
102
+ if (!parsed.success) {
103
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(payload)"}: ${i.message}`).join("; ");
104
+ throw new OnboardingValidationError(
105
+ `Payload failed wire-schema validation: ${detail}`
106
+ );
107
+ }
108
+ const body = JSON.stringify(parsed.data);
109
+ let response;
110
+ try {
111
+ response = await fetchWithTimeout(url, {
112
+ method: "POST",
113
+ headers,
114
+ body,
115
+ timeoutMs,
116
+ fetchImpl
117
+ });
118
+ } catch (err) {
119
+ lastError = new OnboardingNetworkError(err);
120
+ const delay = RETRY_DELAYS_MS[attempt - 1];
121
+ if (attempt < MAX_ATTEMPTS && delay !== void 0) {
122
+ await sleep(delay);
123
+ continue;
124
+ }
125
+ throw lastError;
126
+ }
127
+ if (response.status === 200 || response.status === 202) {
128
+ return parseSuccessBody(response);
129
+ }
130
+ if (response.status === 403) {
131
+ throw new OnboardingBotChallengeError();
132
+ }
133
+ if (response.status === 409) {
134
+ throw new OnboardingConflictError(409);
135
+ }
136
+ if (response.status === 429) {
137
+ const retryAfter = parseRetryAfter(response);
138
+ throw new OnboardingRateLimitError(retryAfter);
139
+ }
140
+ if (response.status === 422) {
141
+ throw new OnboardingValidationError(
142
+ "Server rejected the submission (422). Please check your inputs."
143
+ );
144
+ }
145
+ if (response.status >= 500) {
146
+ lastError = new OnboardingServerError(response.status);
147
+ const delay = RETRY_DELAYS_MS[attempt - 1];
148
+ if (attempt < MAX_ATTEMPTS && delay !== void 0) {
149
+ await sleep(delay);
150
+ continue;
151
+ }
152
+ throw lastError;
153
+ }
154
+ throw new OnboardingServerError(
155
+ response.status,
156
+ `Unexpected response status ${response.status}`
157
+ );
158
+ }
159
+ throw lastError ?? new OnboardingNetworkError();
160
+ }
161
+ var SUBMIT_PATH = "/api/v1/public/onboarding/submit";
162
+ var MAX_ATTEMPTS2 = 3;
163
+ var RETRY_DELAYS_MS2 = [1e3, 2e3];
164
+ var consentSubmitDocumentSchema = z.object({
165
+ legalDocumentId: z.string().min(1, "legalDocumentId is required"),
166
+ versionNo: z.number().int().nonnegative(),
167
+ accepted: z.literal(true, {
168
+ errorMap: () => ({ message: "a consent document must be accepted" })
169
+ }),
170
+ // evidence keys stay snake_case (the only snake bits in the wire shape).
171
+ evidence: z.object({
172
+ scrolled_to_end: z.boolean().optional(),
173
+ opened: z.boolean().optional()
174
+ }).strict()
175
+ }).strict();
176
+ var signupWireSchema = z.object({
177
+ specVersion: z.string().min(1, "specVersion is required"),
178
+ answers: z.record(z.unknown()),
179
+ selectedServices: z.array(z.string()).optional(),
180
+ botToken: z.string().min(1, "botToken is required").optional(),
181
+ consent: z.object({
182
+ tosAccepted: z.literal(true, {
183
+ errorMap: () => ({ message: "Terms of Service must be accepted" })
184
+ }),
185
+ privacyAccepted: z.literal(true, {
186
+ errorMap: () => ({ message: "Privacy Policy must be accepted" })
187
+ }),
188
+ documents: z.array(consentSubmitDocumentSchema).optional()
189
+ }).strict(),
190
+ credentials: z.object({
191
+ email: z.string().email("A valid account email is required"),
192
+ displayName: z.string().trim().min(1).max(200).optional()
193
+ }).strict(),
194
+ organisationName: z.string().trim().min(1).max(200).optional(),
195
+ subdomain: z.string().trim().min(1).max(63)
196
+ }).strict();
197
+ function computeSignupFingerprint(body) {
198
+ const canonical = JSON.stringify([
199
+ body.specVersion,
200
+ // Sort answer keys for stability regardless of insertion order.
201
+ Object.keys(body.answers).sort().map((k) => [k, body.answers[k]]),
202
+ [...body.selectedServices ?? []].sort(),
203
+ body.credentials.email,
204
+ body.credentials.displayName ?? null,
205
+ body.organisationName ?? null,
206
+ body.subdomain,
207
+ body.consent.tosAccepted,
208
+ body.consent.privacyAccepted,
209
+ // Consent documents fold into the fingerprint (sorted by legalDocumentId
210
+ // for order-independence) — re-accepting a changed doc after 409
211
+ // consent_stale is a NEW logical submission and must rotate the key.
212
+ [...body.consent.documents ?? []].sort((a, b) => a.legalDocumentId.localeCompare(b.legalDocumentId)).map((d) => [
213
+ d.legalDocumentId,
214
+ d.versionNo,
215
+ d.evidence.scrolled_to_end ?? false,
216
+ d.evidence.opened ?? false
217
+ ])
218
+ ]);
219
+ let hash = 2166136261;
220
+ for (let i = 0; i < canonical.length; i++) {
221
+ hash ^= canonical.charCodeAt(i);
222
+ hash = Math.imul(hash, 16777619);
223
+ }
224
+ return (hash >>> 0).toString(16).padStart(8, "0");
225
+ }
226
+ function buildUrl2(baseUrl, path) {
227
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
228
+ return `${base}${path}`;
229
+ }
230
+ function parseRetryAfter2(response) {
231
+ const header = response.headers.get("retry-after");
232
+ if (!header) return null;
233
+ const seconds = Number.parseInt(header, 10);
234
+ return Number.isNaN(seconds) ? null : seconds;
235
+ }
236
+ function parseStaleConsentDocuments(value) {
237
+ if (!Array.isArray(value)) return [];
238
+ const out = [];
239
+ for (const entry of value) {
240
+ if (entry === null || typeof entry !== "object") continue;
241
+ const e = entry;
242
+ if (typeof e["legalDocumentId"] !== "string") continue;
243
+ const doc = { legalDocumentId: e["legalDocumentId"] };
244
+ if (typeof e["docType"] === "string") doc.docType = e["docType"];
245
+ if (typeof e["currentVersionNo"] === "number") {
246
+ doc.currentVersionNo = e["currentVersionNo"];
247
+ }
248
+ out.push(doc);
249
+ }
250
+ return out;
251
+ }
252
+ async function sleep2(ms) {
253
+ return new Promise((resolve) => setTimeout(resolve, ms));
254
+ }
255
+ var TERMINAL_STAGES = [
256
+ "prospect",
257
+ "inactive",
258
+ "active"
259
+ ];
260
+ async function parseSubmitSuccess(response) {
261
+ let json;
262
+ try {
263
+ json = await response.json();
264
+ } catch {
265
+ throw new OnboardingServerError(
266
+ response.status,
267
+ `Success status ${response.status} but the submit response body was not JSON \u2014 terminalStage is unknown.`
268
+ );
269
+ }
270
+ if (typeof json === "object" && json !== null && json.accepted === true) {
271
+ const stage = json.terminalStage;
272
+ if (typeof stage === "string" && TERMINAL_STAGES.includes(stage)) {
273
+ const verificationEmailSent = json.verificationEmailSent;
274
+ return {
275
+ accepted: true,
276
+ terminalStage: stage,
277
+ ...typeof verificationEmailSent === "boolean" ? { verificationEmailSent } : {}
278
+ };
279
+ }
280
+ }
281
+ throw new OnboardingServerError(
282
+ response.status,
283
+ `Success status ${response.status} but the submit response did not carry {accepted:true, terminalStage}.`
284
+ );
285
+ }
286
+ async function submitSignup(body, options) {
287
+ const fetchImpl = options.fetch ?? globalThis.fetch;
288
+ const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
289
+ const url = buildUrl2(options.baseUrl, SUBMIT_PATH);
290
+ const base = { ...body };
291
+ if (base["selectedServices"] === void 0) {
292
+ delete base["selectedServices"];
293
+ }
294
+ const headers = {
295
+ "Content-Type": "application/json",
296
+ "Idempotency-Key": options.idempotencyKey
297
+ };
298
+ let lastError = null;
299
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS2; attempt++) {
300
+ options.onAttempt?.(attempt);
301
+ const botToken = options.botToken === void 0 ? void 0 : await resolveBotTokenGuarded(options.botToken);
302
+ const parsed = signupWireSchema.safeParse({ ...base, botToken });
303
+ if (!parsed.success) {
304
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(payload)"}: ${i.message}`).join("; ");
305
+ throw new OnboardingValidationError(
306
+ `Signup payload failed wire-schema validation: ${detail}`
307
+ );
308
+ }
309
+ const wireBody = JSON.stringify(parsed.data);
310
+ let response;
311
+ try {
312
+ response = await fetchWithTimeout(url, {
313
+ method: "POST",
314
+ headers,
315
+ body: wireBody,
316
+ timeoutMs,
317
+ fetchImpl
318
+ });
319
+ } catch (err) {
320
+ lastError = new OnboardingNetworkError(err);
321
+ const delay = RETRY_DELAYS_MS2[attempt - 1];
322
+ if (attempt < MAX_ATTEMPTS2 && delay !== void 0) {
323
+ await sleep2(delay);
324
+ continue;
325
+ }
326
+ throw lastError;
327
+ }
328
+ if (response.status === 200 || response.status === 202) {
329
+ return parseSubmitSuccess(response);
330
+ }
331
+ if (response.status === 403) {
332
+ throw new OnboardingBotChallengeError();
333
+ }
334
+ if (response.status === 400) {
335
+ let code400;
336
+ let message400;
337
+ try {
338
+ const body400 = await response.json();
339
+ code400 = body400["code"];
340
+ const m = body400["message"];
341
+ if (typeof m === "string" && m !== "") message400 = m;
342
+ } catch {
343
+ }
344
+ if (code400 === "consent_incomplete") {
345
+ throw new OnboardingConsentIncompleteError(message400);
346
+ }
347
+ throw new OnboardingValidationError(
348
+ message400 ?? "The server rejected the submission (400). Please review your inputs."
349
+ );
350
+ }
351
+ if (response.status === 409) {
352
+ let code;
353
+ let currentSpecVersion = "unknown";
354
+ let staleDocuments = [];
355
+ try {
356
+ const body409 = await response.json();
357
+ code = body409["code"];
358
+ const v = body409["currentSpecVersion"];
359
+ if (typeof v === "string" && v !== "") currentSpecVersion = v;
360
+ staleDocuments = parseStaleConsentDocuments(body409["staleDocuments"]);
361
+ } catch {
362
+ }
363
+ if (code === "spec_stale") {
364
+ throw new OnboardingSpecStaleError(currentSpecVersion);
365
+ }
366
+ if (code === "consent_stale") {
367
+ throw new OnboardingConsentStaleError(staleDocuments);
368
+ }
369
+ if (code === "subdomain_taken") {
370
+ throw new OnboardingSubdomainTakenError();
371
+ }
372
+ throw new OnboardingConflictError(409);
373
+ }
374
+ if (response.status === 429) {
375
+ throw new OnboardingRateLimitError(parseRetryAfter2(response));
376
+ }
377
+ if (response.status === 422) {
378
+ throw new OnboardingValidationError(
379
+ "The server rejected the submission (422). Please review your inputs."
380
+ );
381
+ }
382
+ if (response.status >= 500) {
383
+ lastError = new OnboardingServerError(response.status);
384
+ const delay = RETRY_DELAYS_MS2[attempt - 1];
385
+ if (attempt < MAX_ATTEMPTS2 && delay !== void 0) {
386
+ await sleep2(delay);
387
+ continue;
388
+ }
389
+ throw lastError;
390
+ }
391
+ throw new OnboardingServerError(
392
+ response.status,
393
+ `Unexpected response status ${response.status}`
394
+ );
395
+ }
396
+ throw lastError ?? new OnboardingNetworkError();
397
+ }
398
+
399
+ export { computePayloadFingerprint, computeSignupFingerprint, consentSubmitDocumentSchema, generateIdempotencyKey, resolveBotToken, signupWireSchema, submitLead, submitSignup };
400
+ //# sourceMappingURL=chunk-BMQINH6O.js.map
401
+ //# sourceMappingURL=chunk-BMQINH6O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/submit/client.ts","../src/submit/signup-client.ts"],"names":["MAX_ATTEMPTS","RETRY_DELAYS_MS","buildUrl","parseRetryAfter","sleep"],"mappings":"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","file":"chunk-BMQINH6O.js","sourcesContent":["/**\n * Submit client for POST /api/v1/public/onboarding/leads.\n *\n * Contract per D540 + hub REST interface 8686dd33:\n * - Idempotency-Key header REQUIRED (uuid v4, generated ONCE per logical\n * submission, REUSED on retries — same key = exactly-once delivery).\n * - Outgoing payload validated through leadsWireSchema (STRICT — unknown\n * keys rejected, matching additionalProperties:false) BEFORE any fetch.\n * - botToken in request body (D537).\n * - Response: { accepted: true } on 200/202 (empty/non-JSON 2xx body is\n * treated as acceptance; a JSON body that does not confirm acceptance\n * throws OnboardingServerError — see parseSuccessBody).\n * - 403 bot_challenge_failed → throw OnboardingBotChallengeError (no retry)\n * - 409 spec_stale (D540) → throw OnboardingSpecStaleError (no retry)\n * - 422 → throw OnboardingValidationError (no retry)\n * - 429 → throw OnboardingRateLimitError (no auto-retry)\n * - 5xx → retry with SAME idempotency key, capped backoff\n * - network error → retry with SAME idempotency key, capped backoff\n *\n * Max retries: 3 attempts total (1 initial + 2 retries).\n * Backoff: 1s, 2s (capped at 2 retries).\n */\n\nimport { validateLeadsPayload } from \"../form/schema.js\";\nimport type { ContactFormValues } from \"../form/schema.js\";\nimport {\n OnboardingBotChallengeError,\n OnboardingConflictError,\n OnboardingNetworkError,\n OnboardingRateLimitError,\n OnboardingServerError,\n OnboardingValidationError,\n} from \"./errors.js\";\nimport { DEFAULT_REQUEST_TIMEOUT_MS, fetchWithTimeout } from \"./http.js\";\n\nconst LEADS_PATH = \"/api/v1/public/onboarding/leads\";\nconst MAX_ATTEMPTS = 3;\nconst RETRY_DELAYS_MS = [1000, 2000] as const;\n\n/**\n * Resolve a bot token inside the retry loop, mapping ANY provider failure to\n * a typed OnboardingBotChallengeError. The provider is usually\n * executeRecaptcha, which throws a RecaptchaError if reCAPTCHA is unreachable\n * — that raw error must NOT escape submitLead/submitSignup (the @throws\n * contract is Onboarding* only) and must not silently burn the retry budget.\n */\nexport async function resolveBotTokenGuarded(\n source: BotTokenSource,\n): Promise<string> {\n try {\n return await resolveBotToken(source);\n } catch (err) {\n throw new OnboardingBotChallengeError(\n err instanceof Error\n ? `Could not obtain a bot-challenge token: ${err.message}`\n : \"Could not obtain a bot-challenge token.\",\n );\n }\n}\n\n/**\n * A bot token, or a provider that mints a FRESH one per attempt.\n *\n * reCAPTCHA tokens are SINGLE-USE — the server consumes them at siteverify.\n * If a 5xx happens AFTER the token was consumed, resending the same token on\n * retry gets 403. Pass a provider function to mint a fresh token per attempt;\n * a bare string is back-compat but the caller then OWNS that risk.\n */\nexport type BotTokenSource = string | (() => string | Promise<string>);\n\nexport async function resolveBotToken(source: BotTokenSource): Promise<string> {\n return typeof source === \"function\" ? source() : source;\n}\n\nexport interface LeadsSubmitOptions {\n baseUrl: string;\n /**\n * reCAPTCHA token (D537), or a provider called ONCE PER ATTEMPT to mint a\n * fresh token (recommended — tokens are single-use; a fixed string on a\n * retried request risks 403 after the first attempt consumed it).\n */\n botToken?: BotTokenSource;\n /**\n * Stable idempotency key for this submission. Generate ONCE with\n * `generateIdempotencyKey()` and pass the same key for retries.\n * The server chains this key through to D538 for exactly-once delivery.\n */\n idempotencyKey: string;\n /** Optional custom fetch implementation (defaults to global fetch). */\n fetch?: typeof globalThis.fetch;\n /**\n * Per-attempt request timeout in ms (default 15s). A stalled intake aborts\n * with OnboardingNetworkError so the widget re-enables instead of hanging.\n */\n timeoutMs?: number;\n /** Optional onAttempt callback for observability (attempt number, 1-based). */\n onAttempt?: (attempt: number) => void;\n}\n\nexport interface LeadsSubmitResult {\n accepted: true;\n}\n\n/**\n * Generate a stable idempotency key for one logical submission.\n * Uses the Web Crypto API (available in all modern browsers + Bun/Node >= 19).\n * Call ONCE per user submission, pass the SAME key on retries.\n */\nexport function generateIdempotencyKey(): string {\n // crypto.randomUUID() is available in all modern browsers, Bun, and Node >= 14.17\n return globalThis.crypto.randomUUID();\n}\n\nfunction buildUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n return `${base}${path}`;\n}\n\nfunction parseRetryAfter(response: Response): number | null {\n const header = response.headers.get(\"retry-after\");\n if (!header) return null;\n const seconds = Number.parseInt(header, 10);\n return Number.isNaN(seconds) ? null : seconds;\n}\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Compute a stable fingerprint of the outgoing lead payload, EXCLUDING\n * botToken (which is fresh per attempt by design).\n *\n * The widget uses this to decide whether a resubmission is a RETRY of the\n * same logical submission (same fingerprint → reuse the Idempotency-Key)\n * or a NEW logical submission (fingerprint changed because the user edited\n * fields → mint a NEW key). This mirrors the server's D538 payload-match\n * guard: same key + different payload is INVALID_ARGUMENT server-side.\n *\n * FNV-1a 32-bit over a stable JSON serialization of [name, email, phone,\n * message]. Collision risk is irrelevant here — a collision only means an\n * unnecessary key reuse across two submissions from the same browser tab.\n */\nexport function computePayloadFingerprint(values: ContactFormValues): string {\n const canonical = JSON.stringify([\n values.companyName,\n values.contactName,\n values.email,\n values.phone ?? null,\n values.message ?? null,\n [...(values.interestModules ?? [])].sort(),\n values.sizeBand ?? null,\n values.attribution ?? null,\n values.privacyNoticeAcknowledged,\n values.marketingOptIn,\n values.intakeKind,\n ]);\n let hash = 0x811c9dc5;\n for (let i = 0; i < canonical.length; i++) {\n hash ^= canonical.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0).toString(16).padStart(8, \"0\");\n}\n\n/**\n * Parse a success-status (200/202) response body.\n *\n * Documented behavior (one rule, tested):\n * - EMPTY body or NON-JSON body → treated as acceptance. The locked\n * contract response is `{accepted:true}` and the status is already\n * 2xx; load balancers / CDNs occasionally strip or replace bodies on\n * 202, and a 2xx from the intake IS the acceptance signal.\n * - A PARSEABLE JSON body that is not `{accepted: true}` (e.g.\n * `{accepted:false}` or an unrelated shape) → OnboardingServerError.\n * A server-signaled soft-rejection on a success status must NOT be\n * masked as success. Not auto-retried (the status was 2xx; the server\n * state is ambiguous — the caller decides).\n */\nasync function parseSuccessBody(\n response: Response,\n): Promise<LeadsSubmitResult> {\n let text: string;\n try {\n text = await response.text();\n } catch {\n // Unreadable body on a 2xx — acceptance per the rule above.\n return { accepted: true };\n }\n if (text.trim() === \"\") {\n return { accepted: true };\n }\n let json: unknown;\n try {\n json = JSON.parse(text);\n } catch {\n // Non-JSON body on a 2xx (LB/CDN interstitial) — acceptance.\n return { accepted: true };\n }\n if (\n typeof json === \"object\" &&\n json !== null &&\n (json as { accepted?: unknown }).accepted === true\n ) {\n return { accepted: true };\n }\n throw new OnboardingServerError(\n response.status,\n `Success status ${response.status} but response body did not confirm acceptance.`,\n );\n}\n\n/**\n * Submit a contact-form lead to the onboarding intake.\n *\n * The outgoing payload is validated through leadsWireSchema in STRICT mode\n * before any network I/O — unknown keys are rejected client-side, exactly\n * mirroring the server's additionalProperties:false.\n *\n * Handles retry semantics: 5xx and network errors are retried up to\n * MAX_ATTEMPTS total, always with the SAME idempotencyKey to guarantee\n * exactly-once server-side processing (D538 idempotency chain).\n *\n * 403, 409, 422 and 429 are NOT retried — caller handles them.\n *\n * @throws {OnboardingValidationError} if the payload fails the strict wire\n * schema (unknown keys, constraint violations) — thrown BEFORE any fetch\n * @throws {OnboardingBotChallengeError} on 403, or if the bot-token provider\n * throws (reCAPTCHA unreachable) on any attempt\n * @throws {OnboardingConflictError} on 409 (leads has no spec_stale concept)\n * @throws {OnboardingRateLimitError} on 429\n * @throws {OnboardingServerError} on 5xx after all retries exhausted, or on\n * a 2xx whose JSON body does not confirm acceptance\n * @throws {OnboardingNetworkError} on network failure OR timeout after all\n * retries exhausted\n * @throws {OnboardingValidationError} on 422 (server-side re-validation)\n */\nexport async function submitLead(\n values: ContactFormValues,\n options: LeadsSubmitOptions,\n): Promise<LeadsSubmitResult> {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const url = buildUrl(options.baseUrl, LEADS_PATH);\n\n // Normalize non-token fields once (empty optionals → omitted). The token\n // is attached PER ATTEMPT below so a retry can carry a FRESH single-use\n // token; the body is therefore serialized inside the loop.\n const base: Record<string, unknown> = { ...values };\n if (base[\"phone\"] === \"\" || base[\"phone\"] === undefined) {\n delete base[\"phone\"];\n }\n if (base[\"message\"] === \"\" || base[\"message\"] === undefined) {\n delete base[\"message\"];\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": options.idempotencyKey,\n };\n\n let lastError: unknown = null;\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n options.onAttempt?.(attempt);\n\n // Mint a fresh token per attempt (single-use tokens; see BotTokenSource).\n // Guarded: a provider failure (reCAPTCHA down) becomes a typed\n // OnboardingBotChallengeError, never a raw RecaptchaError escaping.\n const botToken =\n options.botToken === undefined\n ? undefined\n : await resolveBotTokenGuarded(options.botToken);\n\n // Strict wire-schema validation BEFORE the network I/O of THIS attempt.\n // Extra keys are REJECTED (additionalProperties:false), not stripped.\n const parsed = validateLeadsPayload({ ...base, botToken });\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((i) => `${i.path.join(\".\") || \"(payload)\"}: ${i.message}`)\n .join(\"; \");\n throw new OnboardingValidationError(\n `Payload failed wire-schema validation: ${detail}`,\n );\n }\n const body = JSON.stringify(parsed.data);\n\n let response: Response;\n\n try {\n response = await fetchWithTimeout(url, {\n method: \"POST\",\n headers,\n body,\n timeoutMs,\n fetchImpl,\n });\n } catch (err) {\n // Timeout (abort) and network failures are both retryable transient.\n lastError = new OnboardingNetworkError(err);\n const delay = RETRY_DELAYS_MS[attempt - 1];\n if (attempt < MAX_ATTEMPTS && delay !== undefined) {\n await sleep(delay);\n continue;\n }\n throw lastError;\n }\n\n // 200/202 → success (body parse guarded; see parseSuccessBody)\n if (response.status === 200 || response.status === 202) {\n return parseSuccessBody(response);\n }\n\n // 403 → bot challenge failed — NOT retryable\n if (response.status === 403) {\n throw new OnboardingBotChallengeError();\n }\n\n // 409 on /leads is NOT spec_stale (the leads payload has no specVersion;\n // spec_stale is a /submit-only concept per D540). A leads 409 is a\n // generic conflict (e.g. a D538 same-key concurrent-processing race) —\n // surface OnboardingConflictError so headless consumers switching on\n // `kind` get the right diagnosis, not a \"form has been updated\" message.\n if (response.status === 409) {\n throw new OnboardingConflictError(409);\n }\n\n // 429 → rate limited — NOT auto-retried (caller decides when to retry)\n if (response.status === 429) {\n const retryAfter = parseRetryAfter(response);\n throw new OnboardingRateLimitError(retryAfter);\n }\n\n // 422 → validation error (client-side should have caught this)\n if (response.status === 422) {\n throw new OnboardingValidationError(\n \"Server rejected the submission (422). Please check your inputs.\",\n );\n }\n\n // 5xx → retryable server error\n if (response.status >= 500) {\n lastError = new OnboardingServerError(response.status);\n const delay = RETRY_DELAYS_MS[attempt - 1];\n if (attempt < MAX_ATTEMPTS && delay !== undefined) {\n await sleep(delay);\n continue;\n }\n throw lastError;\n }\n\n // Any other unexpected status\n throw new OnboardingServerError(\n response.status,\n `Unexpected response status ${response.status}`,\n );\n }\n\n // Should not be reachable; guard for type safety\n throw lastError ?? new OnboardingNetworkError();\n}\n","/**\n * Submit client for POST /api/v1/public/onboarding/submit (D540 §2).\n *\n * Contract per the LOCKED hub REST interface (PublicOnboardingSubmitRequest):\n * - Body: { specVersion, answers, selectedServices?, botToken,\n * consent:{tosAccepted:true, privacyAccepted:true},\n * credentials:{email, displayName?}, organisationName?, subdomain }\n * additionalProperties: false — validated STRICTLY client-side before\n * any fetch (unknown keys are rejected, not stripped).\n * - Idempotency-Key header REQUIRED (same D538 discipline as leads:\n * stable across retries of the SAME payload; the widget mints a new\n * key when the payload fingerprint changes).\n * - specVersion policy is reject-stale: 409 → OnboardingSpecStaleError\n * carrying the server's current version. The CALLER refetches the spec\n * and surfaces re-validation to the user — NEVER silent auto-resubmit.\n * - Response: { accepted:true, terminalStage, verificationEmailSent? }.\n *\n * Success-body rule for /submit (DIFFERENT from /leads, deliberately):\n * the response carries REQUIRED routing data (terminalStage decides what\n * the UI shows next), so a 2xx with an empty/unparseable/garbage body is\n * an OnboardingServerError — the SDK will not fabricate a terminal stage.\n * (/leads tolerates body-less 2xx because its only signal IS the status.)\n */\n\nimport { z } from \"zod/v3\";\nimport type { TerminalStage } from \"../spec/model.js\";\nimport type { ConsentSubmitDocument } from \"../consent/controller.js\";\nimport {\n OnboardingBotChallengeError,\n OnboardingConflictError,\n OnboardingConsentIncompleteError,\n OnboardingConsentStaleError,\n OnboardingNetworkError,\n OnboardingRateLimitError,\n OnboardingServerError,\n OnboardingSpecStaleError,\n OnboardingSubdomainTakenError,\n OnboardingValidationError,\n type StaleConsentDocument,\n} from \"./errors.js\";\nimport { type BotTokenSource, resolveBotTokenGuarded } from \"./client.js\";\nimport { DEFAULT_REQUEST_TIMEOUT_MS, fetchWithTimeout } from \"./http.js\";\n\nconst SUBMIT_PATH = \"/api/v1/public/onboarding/submit\";\nconst MAX_ATTEMPTS = 3;\nconst RETRY_DELAYS_MS = [1000, 2000] as const;\n\n/**\n * STRICT wire schema for ONE element of the D543 §4 consent.documents array.\n * `accepted` is const-true (an unaccepted doc is never sent — the consent\n * controller omits it), and `evidence` carries only the gate-relevant flags.\n */\nexport const consentSubmitDocumentSchema = z\n .object({\n legalDocumentId: z.string().min(1, \"legalDocumentId is required\"),\n versionNo: z.number().int().nonnegative(),\n accepted: z.literal(true, {\n errorMap: () => ({ message: \"a consent document must be accepted\" }),\n }),\n // evidence keys stay snake_case (the only snake bits in the wire shape).\n evidence: z\n .object({\n scrolled_to_end: z.boolean().optional(),\n opened: z.boolean().optional(),\n })\n .strict(),\n })\n .strict();\n\n/**\n * STRICT wire schema — mirrors PublicOnboardingSubmitRequest exactly\n * (additionalProperties: false; consent booleans are const-true; passwordless\n * per D1010, with the workspace subdomain required by D836).\n *\n * D543 §4: `consent` extends from booleans to a per-document array. The\n * `documents` array is AUTHORITATIVE (the server re-validates it); the\n * `tosAccepted`/`privacyAccepted` booleans are kept as a DERIVED convenience\n * (D543 \"kept as a derived convenience only\"). `documents` is OPTIONAL on the\n * wire for backward-compat — a pre-ConsentPack onboarder serves no consentPack\n * and submits the booleans alone (D543 \"pre-ConsentPack submits still parse\").\n */\nexport const signupWireSchema = z\n .object({\n specVersion: z.string().min(1, \"specVersion is required\"),\n answers: z.record(z.unknown()),\n selectedServices: z.array(z.string()).optional(),\n botToken: z.string().min(1, \"botToken is required\").optional(),\n consent: z\n .object({\n tosAccepted: z.literal(true, {\n errorMap: () => ({ message: \"Terms of Service must be accepted\" }),\n }),\n privacyAccepted: z.literal(true, {\n errorMap: () => ({ message: \"Privacy Policy must be accepted\" }),\n }),\n documents: z.array(consentSubmitDocumentSchema).optional(),\n })\n .strict(),\n credentials: z\n .object({\n email: z.string().email(\"A valid account email is required\"),\n displayName: z.string().trim().min(1).max(200).optional(),\n })\n .strict(),\n organisationName: z.string().trim().min(1).max(200).optional(),\n subdomain: z.string().trim().min(1).max(63),\n })\n .strict();\n\nexport type SignupWirePayload = z.infer<typeof signupWireSchema>;\n\n/** The payload the caller assembles (botToken attached by the transport). */\nexport interface SignupSubmitBody {\n specVersion: string;\n answers: Record<string, unknown>;\n selectedServices?: string[];\n /**\n * D543 §4 consent. `documents` (the per-doc array) is authoritative when\n * present; the booleans are the derived convenience. `documents` is omitted\n * for a pre-ConsentPack onboarder (backward-compat).\n */\n consent: {\n tosAccepted: true;\n privacyAccepted: true;\n documents?: ConsentSubmitDocument[];\n };\n credentials: { email: string; displayName?: string };\n organisationName?: string;\n subdomain: string;\n}\n\nexport interface SignupSubmitOptions {\n baseUrl: string;\n /**\n * reCAPTCHA token (D537), or a provider called ONCE PER ATTEMPT to mint a\n * fresh token. Tokens are single-use (consumed at siteverify), so a fixed\n * string on a retried request risks 403 after the first attempt consumed\n * it — pass a provider for retry-safe recovery.\n */\n botToken?: BotTokenSource;\n /** Stable idempotency key — same discipline as submitLead. */\n idempotencyKey: string;\n fetch?: typeof globalThis.fetch;\n /**\n * Per-attempt request timeout in ms (default 15s). A stalled intake aborts\n * with OnboardingNetworkError so the widget re-enables instead of hanging.\n */\n timeoutMs?: number;\n onAttempt?: (attempt: number) => void;\n}\n\nexport interface SignupSubmitResult {\n accepted: true;\n /** Stage the existing engine landed the applicant at (D540 §2). */\n terminalStage: TerminalStage;\n verificationEmailSent?: boolean;\n}\n\n/**\n * Stable fingerprint of a signup submission, EXCLUDING botToken (fresh per\n * attempt by design). Same role as computePayloadFingerprint for leads:\n * same fingerprint ⇒ retry (reuse the Idempotency-Key); changed\n * fingerprint ⇒ new logical submission (mint a new key) — mirroring the\n * server's D538 payload-match guard.\n */\nexport function computeSignupFingerprint(body: SignupSubmitBody): string {\n const canonical = JSON.stringify([\n body.specVersion,\n // Sort answer keys for stability regardless of insertion order.\n Object.keys(body.answers)\n .sort()\n .map((k) => [k, body.answers[k]]),\n [...(body.selectedServices ?? [])].sort(),\n body.credentials.email,\n body.credentials.displayName ?? null,\n body.organisationName ?? null,\n body.subdomain,\n body.consent.tosAccepted,\n body.consent.privacyAccepted,\n // Consent documents fold into the fingerprint (sorted by legalDocumentId\n // for order-independence) — re-accepting a changed doc after 409\n // consent_stale is a NEW logical submission and must rotate the key.\n [...(body.consent.documents ?? [])]\n .sort((a, b) => a.legalDocumentId.localeCompare(b.legalDocumentId))\n .map((d) => [\n d.legalDocumentId,\n d.versionNo,\n d.evidence.scrolled_to_end ?? false,\n d.evidence.opened ?? false,\n ]),\n ]);\n let hash = 0x811c9dc5;\n for (let i = 0; i < canonical.length; i++) {\n hash ^= canonical.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0).toString(16).padStart(8, \"0\");\n}\n\nfunction buildUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n return `${base}${path}`;\n}\n\nfunction parseRetryAfter(response: Response): number | null {\n const header = response.headers.get(\"retry-after\");\n if (!header) return null;\n const seconds = Number.parseInt(header, 10);\n return Number.isNaN(seconds) ? null : seconds;\n}\n\n/**\n * Parse the OPTIONAL `staleDocuments` array on a consent_stale 409 body\n * (D543 §4). CANONICAL camelCase ONLY — { legalDocumentId, docType?,\n * currentVersionNo? } — NO snake_case hedging (P2b), matching the sibling\n * spec_stale error's \"no field hedging\" discipline: hedging both cases lets a\n * mismatched server shape pass silently and defeats the canonical-camelCase\n * pin. The server may omit the array entirely (caller then refetches + diffs);\n * an entry lacking a string `legalDocumentId` is skipped. Never throws.\n */\nfunction parseStaleConsentDocuments(value: unknown): StaleConsentDocument[] {\n if (!Array.isArray(value)) return [];\n const out: StaleConsentDocument[] = [];\n for (const entry of value) {\n if (entry === null || typeof entry !== \"object\") continue;\n const e = entry as Record<string, unknown>;\n if (typeof e[\"legalDocumentId\"] !== \"string\") continue;\n const doc: StaleConsentDocument = { legalDocumentId: e[\"legalDocumentId\"] };\n if (typeof e[\"docType\"] === \"string\") doc.docType = e[\"docType\"];\n if (typeof e[\"currentVersionNo\"] === \"number\") {\n doc.currentVersionNo = e[\"currentVersionNo\"];\n }\n out.push(doc);\n }\n return out;\n}\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nconst TERMINAL_STAGES: readonly TerminalStage[] = [\n \"prospect\",\n \"inactive\",\n \"active\",\n];\n\n/** Parse the REQUIRED submit response shape; anything else → ServerError. */\nasync function parseSubmitSuccess(\n response: Response,\n): Promise<SignupSubmitResult> {\n let json: unknown;\n try {\n json = await response.json();\n } catch {\n throw new OnboardingServerError(\n response.status,\n `Success status ${response.status} but the submit response body was not JSON — terminalStage is unknown.`,\n );\n }\n if (\n typeof json === \"object\" &&\n json !== null &&\n (json as { accepted?: unknown }).accepted === true\n ) {\n const stage = (json as { terminalStage?: unknown }).terminalStage;\n if (\n typeof stage === \"string\" &&\n (TERMINAL_STAGES as readonly string[]).includes(stage)\n ) {\n const verificationEmailSent = (\n json as { verificationEmailSent?: unknown }\n ).verificationEmailSent;\n return {\n accepted: true,\n terminalStage: stage as TerminalStage,\n ...(typeof verificationEmailSent === \"boolean\"\n ? { verificationEmailSent }\n : {}),\n };\n }\n }\n throw new OnboardingServerError(\n response.status,\n `Success status ${response.status} but the submit response did not carry {accepted:true, terminalStage}.`,\n );\n}\n\n/**\n * Submit a full signup to the onboarding intake.\n *\n * @throws {OnboardingValidationError} strict wire-schema failure — BEFORE any fetch\n * @throws {OnboardingBotChallengeError} on 403, or if the bot-token provider\n * throws (reCAPTCHA unreachable) on any attempt\n * @throws {OnboardingSpecStaleError} on 409 with code:\"spec_stale\" (caller\n * refetches spec); carries the server's currentSpecVersion\n * @throws {OnboardingConsentStaleError} on 409 with code:\"consent_stale\"\n * (D543 §4 — a required legal doc changed; caller refetches spec + requires\n * re-acceptance of the changed docs, NEVER auto-accepts); carries\n * staleDocuments\n * @throws {OnboardingConsentIncompleteError} on 400 with\n * code:\"consent_incomplete\" (D543 §4 — server found a required doc missing/\n * unaccepted/evidence-insufficient)\n * @throws {OnboardingConflictError} on a 409 that is NOT spec_stale/consent_stale\n * @throws {OnboardingRateLimitError} on 429\n * @throws {OnboardingValidationError} on 422 (server re-validation) or a\n * generic 400 that is NOT consent_incomplete\n * @throws {OnboardingServerError} on 5xx after retries, or a 2xx without\n * the required {accepted:true, terminalStage} body\n * @throws {OnboardingNetworkError} on network failure OR timeout after retries\n */\nexport async function submitSignup(\n body: SignupSubmitBody,\n options: SignupSubmitOptions,\n): Promise<SignupSubmitResult> {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const url = buildUrl(options.baseUrl, SUBMIT_PATH);\n\n // Non-token fields normalized once; token attached PER ATTEMPT (single-use)\n // so a retry carries a fresh token — the body is serialized inside the loop.\n const base: Record<string, unknown> = { ...body };\n if (base[\"selectedServices\"] === undefined) {\n delete base[\"selectedServices\"];\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": options.idempotencyKey,\n };\n\n let lastError: unknown = null;\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n options.onAttempt?.(attempt);\n\n // Guarded: a token-provider failure (reCAPTCHA unreachable on a retry)\n // becomes a typed OnboardingBotChallengeError, never a raw RecaptchaError.\n const botToken =\n options.botToken === undefined\n ? undefined\n : await resolveBotTokenGuarded(options.botToken);\n const parsed = signupWireSchema.safeParse({ ...base, botToken });\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((i) => `${i.path.join(\".\") || \"(payload)\"}: ${i.message}`)\n .join(\"; \");\n throw new OnboardingValidationError(\n `Signup payload failed wire-schema validation: ${detail}`,\n );\n }\n const wireBody = JSON.stringify(parsed.data);\n\n let response: Response;\n try {\n response = await fetchWithTimeout(url, {\n method: \"POST\",\n headers,\n body: wireBody,\n timeoutMs,\n fetchImpl,\n });\n } catch (err) {\n lastError = new OnboardingNetworkError(err);\n const delay = RETRY_DELAYS_MS[attempt - 1];\n if (attempt < MAX_ATTEMPTS && delay !== undefined) {\n await sleep(delay);\n continue;\n }\n throw lastError;\n }\n\n if (response.status === 200 || response.status === 202) {\n return parseSubmitSuccess(response);\n }\n\n if (response.status === 403) {\n throw new OnboardingBotChallengeError();\n }\n\n // 400 — the D543 §4 PINNED consent_incomplete body is\n // { error: true, code: \"consent_incomplete\", ... }\n // The server's authoritative re-validation found a required live document\n // that was missing / unaccepted / evidence-insufficient. Any OTHER 400 is\n // a generic validation error (routes the user to review inputs).\n if (response.status === 400) {\n let code400: unknown;\n let message400: string | undefined;\n try {\n const body400 = (await response.json()) as Record<string, unknown>;\n code400 = body400[\"code\"];\n const m = body400[\"message\"];\n if (typeof m === \"string\" && m !== \"\") message400 = m;\n } catch {\n // keep defaults; a body-less 400 is a generic validation error\n }\n if (code400 === \"consent_incomplete\") {\n throw new OnboardingConsentIncompleteError(message400);\n }\n throw new OnboardingValidationError(\n message400 ??\n \"The server rejected the submission (400). Please review your inputs.\",\n );\n }\n\n // 409 — TWO pinned bodies (D540 spec_stale + D543 §4 consent_stale, its\n // sibling):\n // { error: true, code: \"spec_stale\", currentSpecVersion: string }\n // { error: true, code: \"consent_stale\", staleDocuments?: [...] }\n // ONLY a 409 carrying one of these codes maps to a typed stale error; any\n // other 409 is a generic conflict.\n if (response.status === 409) {\n let code: unknown;\n let currentSpecVersion = \"unknown\";\n let staleDocuments: StaleConsentDocument[] = [];\n try {\n const body409 = (await response.json()) as Record<string, unknown>;\n code = body409[\"code\"];\n const v = body409[\"currentSpecVersion\"];\n if (typeof v === \"string\" && v !== \"\") currentSpecVersion = v;\n staleDocuments = parseStaleConsentDocuments(body409[\"staleDocuments\"]);\n } catch {\n // keep defaults; a body-less 409 is treated as a generic conflict\n }\n if (code === \"spec_stale\") {\n throw new OnboardingSpecStaleError(currentSpecVersion);\n }\n if (code === \"consent_stale\") {\n throw new OnboardingConsentStaleError(staleDocuments);\n }\n if (code === \"subdomain_taken\") {\n throw new OnboardingSubdomainTakenError();\n }\n throw new OnboardingConflictError(409);\n }\n\n if (response.status === 429) {\n throw new OnboardingRateLimitError(parseRetryAfter(response));\n }\n\n if (response.status === 422) {\n throw new OnboardingValidationError(\n \"The server rejected the submission (422). Please review your inputs.\",\n );\n }\n\n if (response.status >= 500) {\n lastError = new OnboardingServerError(response.status);\n const delay = RETRY_DELAYS_MS[attempt - 1];\n if (attempt < MAX_ATTEMPTS && delay !== undefined) {\n await sleep(delay);\n continue;\n }\n throw lastError;\n }\n\n throw new OnboardingServerError(\n response.status,\n `Unexpected response status ${response.status}`,\n );\n }\n\n throw lastError ?? new OnboardingNetworkError();\n}\n"]}