@odla-ai/chapter 0.40.1 → 0.40.3

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.
@@ -1648,7 +1648,14 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
1648
1648
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
1649
1649
  body: JSON.stringify(body)
1650
1650
  });
1651
- if (res.ok) return { ok: true, status: res.status };
1651
+ if (res.ok) {
1652
+ const body2 = typeof res.json === "function" ? await res.json().catch(() => null) : null;
1653
+ return {
1654
+ ok: true,
1655
+ status: res.status,
1656
+ ...typeof body2?.id === "string" && body2.id ? { userId: body2.id } : {}
1657
+ };
1658
+ }
1652
1659
  const healed = heal(res.status);
1653
1660
  const repair = input.repairMetadata ?? {
1654
1661
  ...input.publicMetadata !== void 0 ? { publicMetadata: input.publicMetadata } : {},
@@ -1698,6 +1705,34 @@ async function syncApplicantPrivateMetadata(db, chapter, applicationId, fields,
1698
1705
  return false;
1699
1706
  }
1700
1707
  }
1708
+ var AccountProvisioningError = class extends Error {
1709
+ constructor(code, status) {
1710
+ super(code);
1711
+ this.code = code;
1712
+ this.status = status;
1713
+ }
1714
+ code;
1715
+ status;
1716
+ name = "AccountProvisioningError";
1717
+ };
1718
+ async function provisionApplicantForRoute(db, chapter, applicationId, fields) {
1719
+ try {
1720
+ await provisionApplicant(db, chapter, applicationId, fields);
1721
+ return null;
1722
+ } catch (error) {
1723
+ if (!(error instanceof AccountProvisioningError)) throw error;
1724
+ return {
1725
+ status: error.status,
1726
+ body: {
1727
+ error: {
1728
+ code: error.code,
1729
+ message: error.code === "clerk_secret_unavailable" ? "account provisioning credential is unavailable" : "account provisioning failed"
1730
+ },
1731
+ applicationId
1732
+ }
1733
+ };
1734
+ }
1735
+ }
1701
1736
  async function provisionApplicant(db, chapter, applicationId, fields) {
1702
1737
  const email = typeof fields.email === "string" ? fields.email : "";
1703
1738
  if (!email) return;
@@ -1722,9 +1757,9 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
1722
1757
  } catch {
1723
1758
  }
1724
1759
  if (chapter.account === "none") return;
1760
+ const secret = await getVaultSecret(db, "clerk_secret_key");
1761
+ if (!secret) throw new AccountProvisioningError("clerk_secret_unavailable", 503);
1725
1762
  try {
1726
- const secret = await getVaultSecret(db, "clerk_secret_key");
1727
- if (!secret) return;
1728
1763
  const privateMetadata = applicantPrivateMetadata(chapter, applicationId, fields);
1729
1764
  if (chapter.account === "create") {
1730
1765
  const result = await createClerkUser(secret, {
@@ -1737,11 +1772,13 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
1737
1772
  privateMetadata: applicantPrivateMetadata(chapter, applicationId, fields, true)
1738
1773
  }
1739
1774
  });
1740
- if (result.ok && (!result.existed || result.refreshed)) {
1741
- await markPrivateMetadataSynced(db, applicationId);
1775
+ if (!result.ok || result.existed && !result.refreshed) {
1776
+ throw new AccountProvisioningError("clerk_account_provisioning_failed", 502);
1742
1777
  }
1778
+ await markPrivateMetadataSynced(db, applicationId, result.userId);
1743
1779
  } else {
1744
1780
  const result = await createClerkInvitation(secret, { email });
1781
+ if (!result.ok) throw new AccountProvisioningError("clerk_account_provisioning_failed", 502);
1745
1782
  if (result.existed) {
1746
1783
  const refreshed = await updateClerkUserMetadataByEmail(secret, email, {
1747
1784
  publicMetadata: publicProfileRemoval,
@@ -1750,7 +1787,9 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
1750
1787
  if (refreshed) await markPrivateMetadataSynced(db, applicationId);
1751
1788
  }
1752
1789
  }
1753
- } catch {
1790
+ } catch (error) {
1791
+ if (error instanceof AccountProvisioningError) throw error;
1792
+ throw new AccountProvisioningError("clerk_account_provisioning_failed", 502);
1754
1793
  }
1755
1794
  }
1756
1795
  async function notifyAdminOfApplication(db, env, chapterId, applicationId, fields) {
@@ -1788,12 +1827,16 @@ async function notifyAdminOfApplication(db, env, chapterId, applicationId, field
1788
1827
  }
1789
1828
 
1790
1829
  // src/worker-routes.ts
1791
- async function memberSessionApplication(db, chapterId, email) {
1830
+ async function memberSessionApplication(db, chapterId, identity) {
1792
1831
  let apps = (await db.query({ applications: { $: {
1793
- where: { emailNormalized: normalizeChapterEmail(email) },
1832
+ where: { clerkUserId: identity.userId },
1794
1833
  limit: 20
1795
1834
  } } })).applications;
1796
- if (!apps?.length) apps = (await db.query({ applications: { $: { where: { email }, limit: 20 } } })).applications;
1835
+ if (!apps?.length && identity.email) apps = (await db.query({ applications: { $: {
1836
+ where: { emailNormalized: normalizeChapterEmail(identity.email) },
1837
+ limit: 20
1838
+ } } })).applications;
1839
+ if (!apps?.length && identity.email) apps = (await db.query({ applications: { $: { where: { email: identity.email }, limit: 20 } } })).applications;
1797
1840
  const app = Array.isArray(apps) ? [...apps].sort((a, b) => Number(b.createdAt ?? 0) - Number(a.createdAt ?? 0))[0] : void 0;
1798
1841
  if (!app) return null;
1799
1842
  const meetings = (await db.query({ meetings: { $: { where: { applicationId: app.id, status: "scheduled" }, order: { createdAt: "desc" }, limit: 1 } } })).meetings;
@@ -1824,8 +1867,8 @@ var handleMe = async (req, url, env, ctx) => {
1824
1867
  const role = await ctx.roleFor(db, u);
1825
1868
  const superAdmin = await ctx.isSuperAdminEmail(db, u.email);
1826
1869
  const base = { authorized: isAdminRole(role, ctx.auth), role, superAdmin, email: u.email ?? null };
1827
- if (ctx.chapter.mode !== "chapter" || !u.email) return json(base);
1828
- const member = await memberSessionApplication(db, ctx.chapter.id, u.email);
1870
+ if (ctx.chapter.mode !== "chapter") return json(base);
1871
+ const member = await memberSessionApplication(db, ctx.chapter.id, { userId: u.userId, email: u.email });
1829
1872
  let privateMetadataSynced = false;
1830
1873
  if (member && ctx.chapter.account !== "none" && typeof member.source.clerkPrivateMetadataSyncedAt !== "number") {
1831
1874
  privateMetadataSynced = await syncApplicantPrivateMetadata(
@@ -1974,8 +2017,9 @@ var handleMember = async (req, url, env, ctx) => {
1974
2017
  if (effectiveChapter.sends.adminNotification === "submit") {
1975
2018
  await notifyAdminOfApplication(db, env, chapter.id, result.id, parsed);
1976
2019
  }
1977
- await provisionApplicant(db, effectiveChapter, result.id, parsed);
1978
2020
  }
2021
+ const provisioningFailure = await provisionApplicantForRoute(db, effectiveChapter, result.id, parsed);
2022
+ if (provisioningFailure) return json(provisioningFailure.body, provisioningFailure.status);
1979
2023
  const apps = (await db.query({ applications: { $: { where: { id: result.id }, limit: 1 } } })).applications;
1980
2024
  const canonical2 = Array.isArray(apps) ? apps[0] : void 0;
1981
2025
  const groups = (await db.query({ groups: { $: { where: { id: chapter.id }, limit: 1 } } })).groups;
@@ -6015,8 +6059,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
6015
6059
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
6016
6060
  errors.push(`document.resources.${resource.id}: Chapter sources may author only their own chapter scope`);
6017
6061
  }
6018
- if (sourceType === "built_not_found" && resource.scope.kind === "chapter") {
6019
- errors.push(`document.resources.${resource.id}: Built Not Found sources may author only built_not_found or global scope`);
6062
+ if (sourceType === "built_not_found" && resource.scope.kind === "chapter" && !(options.allowedChapterIds ?? []).includes(resource.scope.chapterId)) {
6063
+ errors.push(`document.resources.${resource.id}: Built Not Found may target only configured Chapters`);
6020
6064
  }
6021
6065
  }
6022
6066
  if (errors.length) return { ok: false, errors };
@@ -6176,12 +6220,13 @@ var handleBuilderLibraryControl = async (req, url, env, ctx) => {
6176
6220
  const checked = validateBuilderLibraryDocument(input.document, {
6177
6221
  sourceType,
6178
6222
  sourceAppId: verified.source.sourceId,
6179
- targetAppId: ctx.chapter.id
6223
+ targetAppId: ctx.chapter.id,
6224
+ ...ctx.chapter.mode === "chapter" ? { allowedChapterIds: [ctx.chapter.id] } : {}
6180
6225
  });
6181
6226
  if (!checked.ok) return json({ ok: false, reason: "invalid_document", errors: checked.errors }, 400);
6182
6227
  const document = checked.value;
6183
- if (ctx.chapter.mode === "chapter" && document.resources.some((resource) => resource.scope.kind !== "global")) {
6184
- return json({ ok: false, reason: "invalid_document", errors: ["Built Not Found may deliver only global resources to a Chapter"] }, 400);
6228
+ if (ctx.chapter.mode === "chapter" && document.resources.some((resource) => resource.scope.kind !== "global" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== ctx.chapter.id))) {
6229
+ return json({ ok: false, reason: "invalid_document", errors: ["Built Not Found may deliver only global or this Chapter's resources"] }, 400);
6185
6230
  }
6186
6231
  if (!document.target || document.target.environment !== env.ODLA_ENV) return json({ ok: false, reason: "wrong_environment" }, 409);
6187
6232
  if (!digest || await builderLibraryDigest(document) !== digest) return json({ ok: false, reason: "invalid_digest" }, 400);
@@ -6287,7 +6332,11 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
6287
6332
  source: { type: "built_not_found", appId: ctx.chapter.id },
6288
6333
  resources: [...local3?.document.resources ?? [], promoted]
6289
6334
  };
6290
- const checked2 = validateBuilderLibraryDocument(document, { sourceType: "built_not_found", sourceAppId: ctx.chapter.id });
6335
+ const checked2 = validateBuilderLibraryDocument(document, {
6336
+ sourceType: "built_not_found",
6337
+ sourceAppId: ctx.chapter.id,
6338
+ allowedChapterIds: ctx.chapter.builderLibrary.sources.map((source2) => source2.sourceId)
6339
+ });
6291
6340
  if (!checked2.ok) return json({ error: "promoted resource is invalid", errors: checked2.errors }, 400);
6292
6341
  try {
6293
6342
  const saved = await saveLocalBuilderLibrary(db, checked2.value, env.ODLA_ENV, expectedRevision2, now2, actorFor(user));
@@ -6302,6 +6351,7 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
6302
6351
  local: local3 ? { revision: local3.revision, document: local3.document } : null,
6303
6352
  sources: controls.map(({ sourceId, revision, digest, document, receivedAt }) => ({ sourceId, revision, digest, document, receivedAt })),
6304
6353
  localAuthoring: ctx.chapter.builderLibrary.localAuthoring,
6354
+ chapterTargets: ctx.chapter.mode === "hub" ? ctx.chapter.builderLibrary.sources.map((source) => ({ chapterId: source.sourceId })) : [{ chapterId: ctx.chapter.id }],
6305
6355
  visibility: { builtNotFoundSeesChapterSources: ctx.chapter.mode === "hub", chaptersSeeSiblingSources: false }
6306
6356
  });
6307
6357
  }
@@ -6320,7 +6370,11 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
6320
6370
  const now = Date.now();
6321
6371
  const actor = actorFor(user);
6322
6372
  const authoritative = withLocalAuthority2(input.document, ctx.chapter.mode, ctx.chapter.id, expectedRevision + 1, now, actor, local3?.document);
6323
- const checked = validateBuilderLibraryDocument(authoritative, { sourceType: ctx.chapter.mode === "hub" ? "built_not_found" : "chapter", sourceAppId: ctx.chapter.id });
6373
+ const checked = validateBuilderLibraryDocument(authoritative, {
6374
+ sourceType: ctx.chapter.mode === "hub" ? "built_not_found" : "chapter",
6375
+ sourceAppId: ctx.chapter.id,
6376
+ ...ctx.chapter.mode === "hub" ? { allowedChapterIds: ctx.chapter.builderLibrary.sources.map((source) => source.sourceId) } : {}
6377
+ });
6324
6378
  if (!checked.ok) return json({ error: "invalid Builder Library", errors: checked.errors }, 400);
6325
6379
  try {
6326
6380
  const saved = await saveLocalBuilderLibrary(db, checked.value, env.ODLA_ENV, expectedRevision, now, actor);