@odla-ai/chapter 0.48.0 → 0.48.1

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.
@@ -386,54 +386,6 @@ function createWorkerContext(options) {
386
386
  };
387
387
  }
388
388
 
389
- // src/worker-routes.ts
390
- var import_crm6 = require("@odla-ai/crm");
391
-
392
- // src/discussion-reference-security.ts
393
- async function authorizeCrmDiscussionReference(req, env, fetcher = fetch) {
394
- const match = req.headers.get("authorization")?.match(/^Bearer ([^\s]+)$/);
395
- const appIncarnation = env.ODLA_APP_INCARNATION;
396
- const expectedTenant = env.ODLA_ENV === "prod" ? env.ODLA_APP_ID : `${env.ODLA_APP_ID}--${env.ODLA_ENV}`;
397
- if (!match || env.ODLA_TENANT !== expectedTenant || typeof appIncarnation !== "string" || !/^[a-f0-9]{32}$/.test(appIncarnation)) return null;
398
- const requestUrl = new URL(req.url);
399
- requestUrl.search = "";
400
- requestUrl.hash = "";
401
- const audience = requestUrl.href;
402
- try {
403
- const url = new URL(
404
- "/registry/discuss/reference-authority/verify",
405
- env.ODLA_PLATFORM
406
- );
407
- const response = await fetcher(url, {
408
- method: "POST",
409
- headers: { "content-type": "application/json" },
410
- body: JSON.stringify({
411
- token: match[1],
412
- appId: env.ODLA_APP_ID,
413
- appIncarnation,
414
- env: env.ODLA_ENV,
415
- product: "crm",
416
- projectCapability: "crm.read",
417
- audience
418
- }),
419
- redirect: "manual"
420
- });
421
- if (!response.ok) return null;
422
- const value = await response.json();
423
- if (value.authority?.appId !== env.ODLA_APP_ID || value.authority.appIncarnation !== appIncarnation || value.authority.appEnv !== env.ODLA_ENV || value.authority.capability !== "product.reference.read" || value.authority.product !== "crm" || value.authority.projectCapability !== "crm.read" || value.authority.audience !== audience || typeof value.actor?.id !== "string" || value.actor.kind !== "human" && value.actor.kind !== "agent" || value.actor.email !== void 0 && typeof value.actor.email !== "string") return null;
424
- return {
425
- userId: value.actor.id,
426
- discussionReferenceScope: "all",
427
- ...value.actor.email ? { email: value.actor.email } : {}
428
- };
429
- } catch {
430
- return null;
431
- }
432
- }
433
-
434
- // src/member.ts
435
- var import_crm2 = require("@odla-ai/crm");
436
-
437
389
  // src/application-id.ts
438
390
  var APPLICATION_ID_DOMAIN = "odla-ai/chapter/application/v1:";
439
391
  async function applicationIdForSubmission(submissionId) {
@@ -445,7 +397,26 @@ async function applicationIdForSubmission(submissionId) {
445
397
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
446
398
  }
447
399
 
400
+ // src/clerk-signup.ts
401
+ async function findSignupAccount(secret, email, fetchImpl = fetch) {
402
+ const response = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(normalizeChapterEmail(email))}&limit=2`, {
403
+ headers: { authorization: `Bearer ${secret}` },
404
+ redirect: "manual",
405
+ signal: AbortSignal.timeout(5e3)
406
+ });
407
+ if (!response.ok) throw new Error("signup account lookup unavailable");
408
+ const users = await response.json();
409
+ if (!Array.isArray(users) || users.length > 1 || users.length === 1 && (typeof users[0]?.id !== "string" || !users[0].id)) {
410
+ throw new Error("signup account lookup unavailable");
411
+ }
412
+ return users[0] ?? null;
413
+ }
414
+ function belongsToSignup(account, applicationId) {
415
+ return typeof applicationId === "string" && applicationId.length > 0 && account.private_metadata?.applicationId === applicationId;
416
+ }
417
+
448
418
  // src/member.ts
419
+ var import_crm2 = require("@odla-ai/crm");
449
420
  var DEFAULT_REQUIRED = ["firstName", "lastName", "email", "referral", "whoYouAre", "message"];
450
421
  var DEFAULT_OPTIONAL = ["referralName", "linkedin", "phone", "state"];
451
422
  var SCHEMA_FIELD = /^[A-Za-z_][A-Za-z0-9_-]*$/;
@@ -549,8 +520,10 @@ async function submitApplication(db, chapter, fields, opts) {
549
520
  if (fields.focus !== void 0) row2.focus = clampArray(fields.focus, app.maxArrayLen);
550
521
  if (opts.groupId) row2.groupId = opts.groupId;
551
522
  if (opts.runtime) row2.stripeRuntime = opts.runtime;
523
+ if (chapter.account !== "none") row2.clerkAccountPending = true;
552
524
  if (typeof fields.email === "string" && isValidEmail(fields.email.trim())) {
553
525
  row2.emailNormalized = normalizeChapterEmail(fields.email);
526
+ row2.signupEmailKey = row2.emailNormalized;
554
527
  }
555
528
  if (opts.admissionGrant?.decision === "waive_interview") {
556
529
  row2.admissionGrantId = opts.admissionGrant.id;
@@ -574,11 +547,21 @@ async function submitApplication(db, chapter, fields, opts) {
574
547
  consumedApplicationId: id6
575
548
  } });
576
549
  }
577
- const { duplicate } = await db.transact(
578
- ops,
579
- opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
580
- );
581
- return { ok: true, id: id6, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
550
+ try {
551
+ const { duplicate } = await db.transact(
552
+ ops,
553
+ opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
554
+ );
555
+ return { ok: true, id: id6, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
556
+ } catch (error) {
557
+ if (error.code === "unique_violation") return {
558
+ ok: false,
559
+ code: "account_already_exists",
560
+ status: 409,
561
+ error: "An account or application already exists for this email. Please sign in to continue."
562
+ };
563
+ throw error;
564
+ }
582
565
  }
583
566
  function joinConfig(group, paymentsReady, tiers = [], conditions = {}) {
584
567
  return {
@@ -601,6 +584,82 @@ function joinConfig(group, paymentsReady, tiers = [], conditions = {}) {
601
584
  };
602
585
  }
603
586
 
587
+ // src/signup-eligibility.ts
588
+ var signupConflict = { code: "account_already_exists", message: "An account or application already exists for this email. Please sign in to continue." };
589
+ async function checkSignupEligibility(db, chapter, fields, submissionId) {
590
+ if (typeof fields.email !== "string" || !isValidEmail(fields.email.trim())) return null;
591
+ const email = normalizeChapterEmail(fields.email);
592
+ const applicationId = submissionId ? await applicationIdForSubmission(submissionId) : void 0;
593
+ const conflict = () => Response.json({ error: signupConflict }, { status: 409 });
594
+ try {
595
+ const data = await db.query({
596
+ applications: { $: { where: { emailNormalized: email }, limit: 2 } },
597
+ $users: { $: { where: { email }, limit: 2 } }
598
+ });
599
+ if ((data.applications ?? []).some((row2) => row2.id !== applicationId)) return conflict();
600
+ const original = (data.applications ?? []).find((row2) => row2.id === applicationId && row2.groupId === chapter.id);
601
+ if ((data.$users ?? []).some((row2) => row2.deleted !== true && row2.id !== original?.clerkUserId)) return conflict();
602
+ if (chapter.account === "none") return null;
603
+ const secret = await getVaultSecret(db, "clerk_secret_key");
604
+ if (!secret) return Response.json({ error: { code: "clerk_secret_unavailable", message: "account provisioning credential is unavailable" } }, { status: 503 });
605
+ const account = await findSignupAccount(secret, email);
606
+ if (account && !(original && (original.clerkUserId === account.id || belongsToSignup(account, original.id)))) return conflict();
607
+ return null;
608
+ } catch {
609
+ return Response.json({ error: { code: "signup_lookup_unavailable", message: "Signup is temporarily unavailable. Please try again." } }, { status: 503 });
610
+ }
611
+ }
612
+ async function rejectUnprovisionedSignup(db, applicationId, status2) {
613
+ await db.transact([{ t: "update", ns: "applications", id: applicationId, attrs: { status: "declined" } }], {
614
+ guards: [{ ns: "applications", id: applicationId, exists: true, equals: { status: status2 }, absent: ["clerkUserId"] }]
615
+ });
616
+ }
617
+
618
+ // src/worker-routes.ts
619
+ var import_crm6 = require("@odla-ai/crm");
620
+
621
+ // src/discussion-reference-security.ts
622
+ async function authorizeCrmDiscussionReference(req, env, fetcher = fetch) {
623
+ const match = req.headers.get("authorization")?.match(/^Bearer ([^\s]+)$/);
624
+ const appIncarnation = env.ODLA_APP_INCARNATION;
625
+ const expectedTenant = env.ODLA_ENV === "prod" ? env.ODLA_APP_ID : `${env.ODLA_APP_ID}--${env.ODLA_ENV}`;
626
+ if (!match || env.ODLA_TENANT !== expectedTenant || typeof appIncarnation !== "string" || !/^[a-f0-9]{32}$/.test(appIncarnation)) return null;
627
+ const requestUrl = new URL(req.url);
628
+ requestUrl.search = "";
629
+ requestUrl.hash = "";
630
+ const audience = requestUrl.href;
631
+ try {
632
+ const url = new URL(
633
+ "/registry/discuss/reference-authority/verify",
634
+ env.ODLA_PLATFORM
635
+ );
636
+ const response = await fetcher(url, {
637
+ method: "POST",
638
+ headers: { "content-type": "application/json" },
639
+ body: JSON.stringify({
640
+ token: match[1],
641
+ appId: env.ODLA_APP_ID,
642
+ appIncarnation,
643
+ env: env.ODLA_ENV,
644
+ product: "crm",
645
+ projectCapability: "crm.read",
646
+ audience
647
+ }),
648
+ redirect: "manual"
649
+ });
650
+ if (!response.ok) return null;
651
+ const value = await response.json();
652
+ if (value.authority?.appId !== env.ODLA_APP_ID || value.authority.appIncarnation !== appIncarnation || value.authority.appEnv !== env.ODLA_ENV || value.authority.capability !== "product.reference.read" || value.authority.product !== "crm" || value.authority.projectCapability !== "crm.read" || value.authority.audience !== audience || typeof value.actor?.id !== "string" || value.actor.kind !== "human" && value.actor.kind !== "agent" || value.actor.email !== void 0 && typeof value.actor.email !== "string") return null;
653
+ return {
654
+ userId: value.actor.id,
655
+ discussionReferenceScope: "all",
656
+ ...value.actor.email ? { email: value.actor.email } : {}
657
+ };
658
+ } catch {
659
+ return null;
660
+ }
661
+ }
662
+
604
663
  // src/tiers.ts
605
664
  function tierIsFree(tier) {
606
665
  return !(tier.priceCents > 0) && !tier.stripePriceId;
@@ -3580,13 +3639,13 @@ async function memberSessionApplication(db, chapterId, runtime, identity) {
3580
3639
  const normalizedRows = await lookup({ emailNormalized: normalized });
3581
3640
  const legacyRows = await lookup({ email: identity.email });
3582
3641
  apps = [...new Map([...normalizedRows, ...legacyRows].map((row2) => [row2.id, row2])).values()];
3583
- if (apps.length !== 1 || apps[0]?.clerkUserId !== void 0) return null;
3642
+ if (apps.length !== 1 || apps[0]?.clerkUserId !== void 0 || apps[0]?.clerkAccountPending === true || ["declined", "canceled", "refunded"].includes(String(apps[0]?.status))) return null;
3584
3643
  emailFallback = true;
3585
3644
  }
3586
3645
  let app = apps.sort((a, b) => Number(b.createdAt ?? 0) - Number(a.createdAt ?? 0))[0];
3587
3646
  if (!app) return null;
3588
3647
  if (emailFallback) {
3589
- const keys3 = ["groupId", "stripeRuntime", "email", "emailNormalized", "status"];
3648
+ const keys3 = ["groupId", "stripeRuntime", "email", "emailNormalized", "status", "clerkAccountPending"];
3590
3649
  try {
3591
3650
  await db.transact([{ t: "update", ns: "applications", id: String(app.id), attrs: { clerkUserId: identity.userId } }], {
3592
3651
  mutationId: `member-account-link:${app.id}:${identity.userId}`,
@@ -3620,7 +3679,11 @@ async function memberSessionApplication(db, chapterId, runtime, identity) {
3620
3679
  }
3621
3680
 
3622
3681
  // src/clerk.ts
3623
- var heal = (status2) => status2 === 422 ? { ok: true, status: status2, existed: true } : { ok: false, status: status2 };
3682
+ async function rejectedCreate(res) {
3683
+ const body = typeof res.json === "function" ? await res.json().catch(() => null) : null;
3684
+ const existed = Array.isArray(body?.errors) && body.errors.some((error) => error?.code === "form_identifier_exists" || error?.code === "duplicate_record");
3685
+ return { ok: false, status: res.status, ...existed ? { existed: true } : {} };
3686
+ }
3624
3687
  function clerkInviteRequest(input) {
3625
3688
  return {
3626
3689
  path: "/v1/invitations",
@@ -3639,7 +3702,7 @@ async function createClerkInvitation(secretKey, input, fetchImpl = fetch) {
3639
3702
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
3640
3703
  body: JSON.stringify(body)
3641
3704
  });
3642
- return res.ok ? { ok: true, status: res.status } : heal(res.status);
3705
+ return res.ok ? { ok: true, status: res.status } : rejectedCreate(res);
3643
3706
  }
3644
3707
  function clerkUserRequest(input) {
3645
3708
  return {
@@ -3665,17 +3728,6 @@ async function updateClerkUserMetadata(secretKey, userId, input, fetchImpl = fet
3665
3728
  });
3666
3729
  return res.ok;
3667
3730
  }
3668
- async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl = fetch) {
3669
- const id6 = await existingClerkUserId(secretKey, email, fetchImpl);
3670
- return id6 ? updateClerkUserMetadata(secretKey, id6, input, fetchImpl) : false;
3671
- }
3672
- async function existingClerkUserId(secretKey, email, fetchImpl) {
3673
- const auth = { authorization: `Bearer ${secretKey}` };
3674
- const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=2`, { headers: auth });
3675
- if (!found.ok) return null;
3676
- const users = await found.json().catch(() => null);
3677
- return Array.isArray(users) && users.length === 1 && typeof users[0]?.id === "string" ? users[0].id : null;
3678
- }
3679
3731
  async function createClerkUser(secretKey, input, fetchImpl = fetch) {
3680
3732
  const { path, body } = clerkUserRequest(input);
3681
3733
  const res = await fetchImpl(`https://api.clerk.com${path}`, {
@@ -3691,20 +3743,10 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
3691
3743
  ...typeof body2?.id === "string" && body2.id ? { userId: body2.id } : {}
3692
3744
  };
3693
3745
  }
3694
- const errorBody = res.status === 400 && typeof res.json === "function" ? await res.json().catch(() => null) : null;
3695
- const existingIdentifier = Array.isArray(errorBody?.errors) && errorBody.errors.some((error) => error.code === "form_identifier_exists");
3696
- const healed = existingIdentifier ? { ok: true, status: res.status, existed: true } : heal(res.status);
3697
- const repair = input.repairMetadata ?? {
3698
- ...input.publicMetadata !== void 0 ? { publicMetadata: input.publicMetadata } : {},
3699
- ...input.privateMetadata !== void 0 ? { privateMetadata: input.privateMetadata } : {}
3700
- };
3701
- if (!healed.existed || repair.publicMetadata === void 0 && repair.privateMetadata === void 0) return healed;
3702
- const userId = await existingClerkUserId(secretKey, input.email, fetchImpl).catch(() => null);
3703
- const refreshed = userId ? await updateClerkUserMetadata(secretKey, userId, {
3704
- ...repair.publicMetadata !== void 0 ? { publicMetadata: repair.publicMetadata } : {},
3705
- ...repair.privateMetadata !== void 0 ? { privateMetadata: repair.privateMetadata } : {}
3706
- }, fetchImpl).catch(() => false) : false;
3707
- return { ...healed, refreshed, ...refreshed && userId ? { userId } : {} };
3746
+ const failure = await rejectedCreate(res);
3747
+ if (!failure.existed) return failure;
3748
+ const account = await findSignupAccount(secretKey, input.email, fetchImpl).catch(() => null);
3749
+ return account && belongsToSignup(account, input.privateMetadata?.applicationId) ? { ok: true, status: res.status, existed: true, userId: account.id } : failure;
3708
3750
  }
3709
3751
 
3710
3752
  // src/crm-provisioned-identity.ts
@@ -3759,6 +3801,7 @@ async function markPrivateMetadataSynced(db, applicationId, clerkUserId) {
3759
3801
  id: applicationId,
3760
3802
  attrs: {
3761
3803
  clerkPrivateMetadataSyncedAt: Date.now(),
3804
+ clerkAccountPending: false,
3762
3805
  ...clerkUserId ? { clerkUserId } : {}
3763
3806
  }
3764
3807
  }]);
@@ -3813,7 +3856,7 @@ async function provisionApplicantForRoute(db, chapter, applicationId, fields) {
3813
3856
  body: {
3814
3857
  error: {
3815
3858
  code: error.code,
3816
- message: error.code === "clerk_secret_unavailable" ? "account provisioning credential is unavailable" : "account provisioning failed"
3859
+ message: error.code === "account_already_exists" ? signupConflict.message : error.code === "clerk_secret_unavailable" ? "account provisioning credential is unavailable" : "account provisioning failed"
3817
3860
  },
3818
3861
  applicationId
3819
3862
  }
@@ -3823,7 +3866,53 @@ async function provisionApplicantForRoute(db, chapter, applicationId, fields) {
3823
3866
  async function provisionApplicant(db, chapter, applicationId, fields) {
3824
3867
  const email = typeof fields.email === "string" ? fields.email : "";
3825
3868
  if (!email) return;
3869
+ if (typeof fields.clerkPrivateMetadataSyncedAt === "number" && typeof fields.clerkUserId === "string") return;
3826
3870
  const stringValue = (value) => typeof value === "string" ? value : void 0;
3871
+ if (chapter.account !== "none") {
3872
+ const secret = await getVaultSecret(db, "clerk_secret_key");
3873
+ if (!secret) throw new AccountProvisioningError("clerk_secret_unavailable", 503);
3874
+ try {
3875
+ const existing = await findSignupAccount(secret, email);
3876
+ if (existing) {
3877
+ if (existing.id !== fields.clerkUserId && !belongsToSignup(existing, applicationId)) {
3878
+ throw new AccountProvisioningError("account_already_exists", 409);
3879
+ }
3880
+ await markPrivateMetadataSynced(db, applicationId, existing.id);
3881
+ return;
3882
+ }
3883
+ const privateMetadata = applicantPrivateMetadata(chapter, applicationId, fields);
3884
+ if (chapter.account === "create") {
3885
+ const result = await createClerkUser(secret, {
3886
+ email,
3887
+ firstName: stringValue(fields.firstName),
3888
+ lastName: stringValue(fields.lastName),
3889
+ privateMetadata
3890
+ });
3891
+ if (!result.ok) throw new AccountProvisioningError(
3892
+ result.existed ? "account_already_exists" : "clerk_account_provisioning_failed",
3893
+ result.existed ? 409 : 502
3894
+ );
3895
+ if (!result.userId) throw new AccountProvisioningError("clerk_account_provisioning_failed", 502);
3896
+ await markPrivateMetadataSynced(db, applicationId, result.userId);
3897
+ } else {
3898
+ if (typeof fields.clerkInvitationSentAt === "number") return;
3899
+ const result = await createClerkInvitation(secret, { email });
3900
+ if (!result.ok) throw new AccountProvisioningError(
3901
+ result.existed ? "account_already_exists" : "clerk_account_provisioning_failed",
3902
+ result.existed ? 409 : 502
3903
+ );
3904
+ await db.transact([{
3905
+ t: "update",
3906
+ ns: "applications",
3907
+ id: applicationId,
3908
+ attrs: { clerkInvitationSentAt: Date.now(), clerkAccountPending: false }
3909
+ }]);
3910
+ }
3911
+ } catch (error) {
3912
+ if (error instanceof AccountProvisioningError) throw error;
3913
+ throw new AccountProvisioningError("clerk_account_provisioning_failed", 502);
3914
+ }
3915
+ }
3827
3916
  const extra = {};
3828
3917
  for (const field of chapter.application.crmFields) {
3829
3918
  if (fields[field] !== void 0) extra[field] = fields[field];
@@ -3842,44 +3931,9 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
3842
3931
  extra
3843
3932
  }
3844
3933
  );
3934
+ if (chapter.account === "create") await linkProvisionedApplicant(db, chapter.id, applicationId);
3845
3935
  } catch {
3846
3936
  }
3847
- if (chapter.account === "none") return;
3848
- const secret = await getVaultSecret(db, "clerk_secret_key");
3849
- if (!secret) throw new AccountProvisioningError("clerk_secret_unavailable", 503);
3850
- try {
3851
- const privateMetadata = applicantPrivateMetadata(chapter, applicationId, fields);
3852
- if (chapter.account === "create") {
3853
- const result = await createClerkUser(secret, {
3854
- email,
3855
- firstName: stringValue(fields.firstName),
3856
- lastName: stringValue(fields.lastName),
3857
- privateMetadata,
3858
- repairMetadata: {
3859
- publicMetadata: publicProfileRemoval,
3860
- privateMetadata: applicantPrivateMetadata(chapter, applicationId, fields, true)
3861
- }
3862
- });
3863
- if (!result.ok || result.existed && !result.refreshed) {
3864
- throw new AccountProvisioningError("clerk_account_provisioning_failed", 502);
3865
- }
3866
- await markPrivateMetadataSynced(db, applicationId, result.userId);
3867
- await linkProvisionedApplicant(db, chapter.id, applicationId).catch(() => false);
3868
- } else {
3869
- const result = await createClerkInvitation(secret, { email });
3870
- if (!result.ok) throw new AccountProvisioningError("clerk_account_provisioning_failed", 502);
3871
- if (result.existed) {
3872
- const refreshed = await updateClerkUserMetadataByEmail(secret, email, {
3873
- publicMetadata: publicProfileRemoval,
3874
- privateMetadata: applicantPrivateMetadata(chapter, applicationId, fields, true)
3875
- });
3876
- if (refreshed) await markPrivateMetadataSynced(db, applicationId);
3877
- }
3878
- }
3879
- } catch (error) {
3880
- if (error instanceof AccountProvisioningError) throw error;
3881
- throw new AccountProvisioningError("clerk_account_provisioning_failed", 502);
3882
- }
3883
3937
  }
3884
3938
  async function notifyAdminOfApplication(db, env, chapterId, applicationId, fields) {
3885
3939
  try {
@@ -4053,6 +4107,8 @@ var handleMember = async (req, url, env, ctx) => {
4053
4107
  }
4054
4108
  const submissionId = typeof parsed.submissionId === "string" ? parsed.submissionId : void 0;
4055
4109
  const db = ctx.makeDb(env);
4110
+ const signupFailure = await checkSignupEligibility(db, chapter, parsed, submissionId);
4111
+ if (signupFailure) return signupFailure;
4056
4112
  const activeControl = chapter.signupControl ? await loadActiveSignupControl(db, chapter.id, env.ODLA_ENV) : null;
4057
4113
  const effectiveChapter = activeControl ? { ...chapter, application: controlledApplication(chapter.application, activeControl.document) } : chapter;
4058
4114
  const offeredTierIds = typeof parsed.tierId === "string" && parsed.tierId ? (await loadTiers(db, { id: chapter.id })).map((tier) => tier.id) : void 0;
@@ -4071,7 +4127,7 @@ var handleMember = async (req, url, env, ctx) => {
4071
4127
  ...effectiveGrant?.decision === "waive_interview" ? { admissionGrant: effectiveGrant } : {},
4072
4128
  ...giftOffer ? { giftOfferId: giftOffer.id } : {}
4073
4129
  });
4074
- if (!result.ok) return json({ error: result.error }, 400);
4130
+ if (!result.ok) return json({ error: result.error, ...result.code ? { code: result.code } : {} }, result.status ?? 400);
4075
4131
  const apps = (await db.query({ applications: { $: { where: { id: result.id }, limit: 1 } } })).applications;
4076
4132
  const canonical2 = Array.isArray(apps) ? apps[0] : void 0;
4077
4133
  if (!canonical2 || canonical2.groupId !== chapter.id) return json({ error: "application state unavailable" }, 503);
@@ -4084,13 +4140,14 @@ var handleMember = async (req, url, env, ctx) => {
4084
4140
  if (normalizeChapterEmail(String(canonical2.email ?? "")) !== normalizeChapterEmail(String(parsed.email ?? ""))) {
4085
4141
  return json({ error: "submission belongs to a different application", code: "application_submission_conflict" }, 409);
4086
4142
  }
4087
- if (!result.duplicate) {
4088
- if (effectiveChapter.sends.adminNotification === "submit") {
4089
- await notifyAdminOfApplication(db, env, chapter.id, result.id, canonical2);
4090
- }
4091
- }
4092
4143
  const provisioningFailure = await provisionApplicantForRoute(db, effectiveChapter, result.id, canonical2);
4093
- if (provisioningFailure) return json(provisioningFailure.body, provisioningFailure.status);
4144
+ if (provisioningFailure) {
4145
+ if (provisioningFailure.status === 409) await rejectUnprovisionedSignup(db, result.id, canonical2.status);
4146
+ return json(provisioningFailure.body, provisioningFailure.status);
4147
+ }
4148
+ if (!result.duplicate && effectiveChapter.sends.adminNotification === "submit") {
4149
+ await notifyAdminOfApplication(db, env, chapter.id, result.id, canonical2);
4150
+ }
4094
4151
  const groups = (await db.query({ groups: { $: { where: { id: chapter.id }, limit: 1 } } })).groups;
4095
4152
  const group = Array.isArray(groups) ? groups[0] : void 0;
4096
4153
  const state = canonical2 && group ? await continueJoin(db, ctx, env, url.origin, canonical2, group) : null;