@odla-ai/chapter 0.47.4 → 0.47.6

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.
@@ -340,8 +340,8 @@ function tierPayable(tier, group, hasSecretKey) {
340
340
  function byOrder(a, b) {
341
341
  return a.sortOrder - b.sortOrder || a.id.localeCompare(b.id);
342
342
  }
343
- function resolveTiers(group, rows6 = []) {
344
- const stored = rows6.filter((tier) => tier.active);
343
+ function resolveTiers(group, rows5 = []) {
344
+ const stored = rows5.filter((tier) => tier.active);
345
345
  if (stored.length) return [...stored].sort(byOrder);
346
346
  return [{
347
347
  id: "standard",
@@ -387,15 +387,15 @@ function rowToTier(row, groupId) {
387
387
  }
388
388
  async function loadTiers(db, group) {
389
389
  const groupId = String(group.id ?? "");
390
- let rows6;
390
+ let rows5;
391
391
  try {
392
- rows6 = (await db.query({
392
+ rows5 = (await db.query({
393
393
  tiers: { $: { where: { groupId }, order: { sortOrder: "asc" }, limit: 100 } }
394
394
  })).tiers;
395
395
  } catch {
396
- rows6 = void 0;
396
+ rows5 = void 0;
397
397
  }
398
- const list = Array.isArray(rows6) ? rows6.map((row) => rowToTier(row, groupId)) : [];
398
+ const list = Array.isArray(rows5) ? rows5.map((row) => rowToTier(row, groupId)) : [];
399
399
  return resolveTiers(group, list);
400
400
  }
401
401
  async function loadOfferableTiers(db, group, hasSecretKey) {
@@ -1454,27 +1454,27 @@ async function hasSignupControl(db, targetId, environment, active) {
1454
1454
  if (db.aggregate) {
1455
1455
  return Number((await db.aggregate("signupControls", { count: true }, { where })).count ?? 0) > 0;
1456
1456
  }
1457
- const rows6 = (await db.query({ signupControls: {} })).signupControls;
1458
- return Array.isArray(rows6) && rows6.some((row) => row.targetId === targetId && row.environment === environment && (!active || row.active === true));
1457
+ const rows5 = (await db.query({ signupControls: {} })).signupControls;
1458
+ return Array.isArray(rows5) && rows5.some((row) => row.targetId === targetId && row.environment === environment && (!active || row.active === true));
1459
1459
  }
1460
1460
  async function loadLatestSignupControl(db, targetId, environment) {
1461
1461
  if (!await hasSignupControl(db, targetId, environment)) return null;
1462
- const rows6 = (await db.query({ signupControls: { $: {
1462
+ const rows5 = (await db.query({ signupControls: { $: {
1463
1463
  where: { targetId, environment },
1464
1464
  order: { revision: "desc" },
1465
1465
  limit: 1
1466
1466
  } } })).signupControls;
1467
- const latest = Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0] : void 0;
1467
+ const latest = Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0] : void 0;
1468
1468
  return asStored(latest);
1469
1469
  }
1470
1470
  async function loadActiveSignupControl(db, targetId, environment) {
1471
1471
  if (!await hasSignupControl(db, targetId, environment, true)) return null;
1472
- const rows6 = (await db.query({ signupControls: { $: {
1472
+ const rows5 = (await db.query({ signupControls: { $: {
1473
1473
  where: { targetId, environment, active: true },
1474
1474
  order: { revision: "desc" },
1475
1475
  limit: 1
1476
1476
  } } })).signupControls;
1477
- const active = Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0] : void 0;
1477
+ const active = Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0] : void 0;
1478
1478
  return asStored(active);
1479
1479
  }
1480
1480
  function controlledApplication(base, document) {
@@ -2850,33 +2850,33 @@ var control = (row) => row && row.document && typeof row.document === "object" ?
2850
2850
  var local = (row) => row && row.document && typeof row.document === "object" ? row : null;
2851
2851
  async function exists(db, ns, where) {
2852
2852
  if (db.aggregate) return Number((await db.aggregate(ns, { count: true }, { where })).count ?? 0) > 0;
2853
- const rows6 = (await db.query({ [ns]: {} }))[ns] ?? [];
2854
- return rows6.some((row) => Object.entries(where).every(([key, value]) => row[key] === value));
2853
+ const rows5 = (await db.query({ [ns]: {} }))[ns] ?? [];
2854
+ return rows5.some((row) => Object.entries(where).every(([key, value]) => row[key] === value));
2855
2855
  }
2856
2856
  async function loadActiveMemberContentControl(db, targetId, environment) {
2857
2857
  const where = { targetId, environment, active: true };
2858
2858
  if (!await exists(db, "memberContentControls", where)) return null;
2859
- const rows6 = (await db.query({ memberContentControls: { $: {
2859
+ const rows5 = (await db.query({ memberContentControls: { $: {
2860
2860
  where,
2861
2861
  order: { revision: "desc" },
2862
2862
  limit: 1
2863
2863
  } } })).memberContentControls;
2864
- return control(Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
2864
+ return control(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
2865
2865
  }
2866
2866
  async function loadLatestMemberContentControl(db, targetId, environment) {
2867
2867
  const where = { targetId, environment };
2868
2868
  if (!await exists(db, "memberContentControls", where)) return null;
2869
- const rows6 = (await db.query({ memberContentControls: { $: {
2869
+ const rows5 = (await db.query({ memberContentControls: { $: {
2870
2870
  where,
2871
2871
  order: { revision: "desc" },
2872
2872
  limit: 1
2873
2873
  } } })).memberContentControls;
2874
- return control(Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
2874
+ return control(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
2875
2875
  }
2876
2876
  async function loadLocalMemberContent(db, chapterId, environment) {
2877
2877
  const id = `member-content-local:${environment}`;
2878
- const rows6 = (await db.query({ memberContentLocal: { $: { where: { id }, limit: 1 } } })).memberContentLocal;
2879
- return local(Array.isArray(rows6) ? rows6[0] : void 0);
2878
+ const rows5 = (await db.query({ memberContentLocal: { $: { where: { id }, limit: 1 } } })).memberContentLocal;
2879
+ return local(Array.isArray(rows5) ? rows5[0] : void 0);
2880
2880
  }
2881
2881
  async function materializeMemberContentControl(db, envelope, previous, receivedAt) {
2882
2882
  const { document, digest: digest2 } = envelope;
@@ -2931,12 +2931,12 @@ async function groupAndTiers(db, chapterId) {
2931
2931
  }
2932
2932
  async function memberTierId(db, chapterId, email) {
2933
2933
  if (!email) return null;
2934
- let rows6 = (await db.query({ applications: { $: {
2934
+ let rows5 = (await db.query({ applications: { $: {
2935
2935
  where: { emailNormalized: normalizeAdmissionEmail(email) },
2936
2936
  limit: 20
2937
2937
  } } })).applications;
2938
- if (!rows6?.length) rows6 = (await db.query({ applications: { $: { where: { email }, limit: 20 } } })).applications;
2939
- const latest = Array.isArray(rows6) ? [...rows6].filter((row) => String(row.groupId ?? chapterId) === chapterId).sort((a, b) => Number(b.createdAt ?? 0) - Number(a.createdAt ?? 0))[0] : void 0;
2938
+ if (!rows5?.length) rows5 = (await db.query({ applications: { $: { where: { email }, limit: 20 } } })).applications;
2939
+ const latest = Array.isArray(rows5) ? [...rows5].filter((row) => String(row.groupId ?? chapterId) === chapterId).sort((a, b) => Number(b.createdAt ?? 0) - Number(a.createdAt ?? 0))[0] : void 0;
2940
2940
  return typeof latest?.tierId === "string" && latest.tierId ? latest.tierId : null;
2941
2941
  }
2942
2942
  async function activeDocuments(db, chapterId, environment) {
@@ -3662,8 +3662,8 @@ function makeCalendar(env) {
3662
3662
  }
3663
3663
  async function firstRow(db, ns, q) {
3664
3664
  const res = await db.query({ [ns]: { $: q } });
3665
- const rows6 = res[ns];
3666
- return Array.isArray(rows6) ? rows6[0] : void 0;
3665
+ const rows5 = res[ns];
3666
+ return Array.isArray(rows5) ? rows5[0] : void 0;
3667
3667
  }
3668
3668
  async function computeSlots(cal, cfg) {
3669
3669
  const { from, to } = slotWindow(Date.now(), cfg.windowDays);
@@ -4063,8 +4063,8 @@ function createStripeProvider(config) {
4063
4063
  const sk = requireSecret(secretKey);
4064
4064
  const charges = await stripeCall(sk, "GET", "/v1/charges", { customer: input.customerId, limit: 100 });
4065
4065
  if (!charges.ok) fail("charges list", charges);
4066
- const rows6 = charges.body.data ?? [];
4067
- const paid = rows6.filter((c) => c.status === "succeeded" && c.refunded !== true);
4066
+ const rows5 = charges.body.data ?? [];
4067
+ const paid = rows5.filter((c) => c.status === "succeeded" && c.refunded !== true);
4068
4068
  const charge = paid[paid.length - 1];
4069
4069
  if (!charge) {
4070
4070
  const err = new Error("no paid charge to refund");
@@ -4172,8 +4172,8 @@ async function issueGiftClaim(db, intent, provider, now) {
4172
4172
  throw new Error("gift issuance conflict");
4173
4173
  }
4174
4174
  }
4175
- function buyerGiftClaims(rows6, now) {
4176
- return rows6.map((row) => {
4175
+ function buyerGiftClaims(rows5, now) {
4176
+ return rows5.map((row) => {
4177
4177
  const stored = String(row.status ?? "pending");
4178
4178
  const status = stored === "pending" && Number(row.codeExpiresAt) <= now ? "expired" : stored;
4179
4179
  return {
@@ -4465,8 +4465,8 @@ function classifyMemberStripeEvent(identity, target, hasProvenLegacyLocalLink) {
4465
4465
 
4466
4466
  // src/stripe-webhook-partition.ts
4467
4467
  var firstRow2 = async (db, ns, q) => {
4468
- const rows6 = (await db.query({ [ns]: { $: q } }))[ns];
4469
- return Array.isArray(rows6) ? rows6[0] : void 0;
4468
+ const rows5 = (await db.query({ [ns]: { $: q } }))[ns];
4469
+ return Array.isArray(rows5) ? rows5[0] : void 0;
4470
4470
  };
4471
4471
  var stripeEventDigest = async (rawBody) => {
4472
4472
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(rawBody));
@@ -4536,8 +4536,8 @@ async function replayedStripeReceipt(db, eventId, digest2, event) {
4536
4536
  // src/worker-routes-payments.ts
4537
4537
  async function firstRow3(db, ns, q) {
4538
4538
  const res = await db.query({ [ns]: { $: q } });
4539
- const rows6 = res[ns];
4540
- return Array.isArray(rows6) ? rows6[0] : void 0;
4539
+ const rows5 = res[ns];
4540
+ return Array.isArray(rows5) ? rows5[0] : void 0;
4541
4541
  }
4542
4542
  async function findApplication(db, event) {
4543
4543
  if ("applicationId" in event && event.applicationId) {
@@ -5003,8 +5003,8 @@ async function upcomingEvents(env) {
5003
5003
  const res = await cal.availability.upcoming();
5004
5004
  return res.events.map((e) => ({ eventId: e.eventId, status: e.status, startAt: e.startAt, endAt: e.endAt }));
5005
5005
  }
5006
- function toReconcile(rows6) {
5007
- return rows6.map((m) => ({
5006
+ function toReconcile(rows5) {
5007
+ return rows5.map((m) => ({
5008
5008
  id: String(m.id),
5009
5009
  applicationId: String(m.applicationId),
5010
5010
  googleEventId: typeof m.googleEventId === "string" ? m.googleEventId : null,
@@ -5026,12 +5026,12 @@ var handleAdminMeetings = async (req, url, env, ctx) => {
5026
5026
  const query = await db.query({
5027
5027
  meetings: { $: { where: all ? {} : { status: "scheduled" }, order: { startAt: "asc" }, limit: 500 } }
5028
5028
  });
5029
- let rows6 = Array.isArray(query.meetings) ? query.meetings : [];
5030
- if (Number.isFinite(from)) rows6 = rows6.filter((m) => Number(m.startAt ?? 0) >= from);
5031
- if (Number.isFinite(to)) rows6 = rows6.filter((m) => Number(m.startAt ?? 0) <= to);
5029
+ let rows5 = Array.isArray(query.meetings) ? query.meetings : [];
5030
+ if (Number.isFinite(from)) rows5 = rows5.filter((m) => Number(m.startAt ?? 0) >= from);
5031
+ if (Number.isFinite(to)) rows5 = rows5.filter((m) => Number(m.startAt ?? 0) <= to);
5032
5032
  let decisions = [];
5033
5033
  try {
5034
- decisions = reconcileMeetings(toReconcile(rows6), await upcomingEvents(env), Date.now());
5034
+ decisions = reconcileMeetings(toReconcile(rows5), await upcomingEvents(env), Date.now());
5035
5035
  } catch {
5036
5036
  }
5037
5037
  const patched = /* @__PURE__ */ new Map();
@@ -5058,7 +5058,7 @@ var handleAdminMeetings = async (req, url, env, ctx) => {
5058
5058
  };
5059
5059
  const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
5060
5060
  const timezone = resolveScheduling(group?.schedulingJson).timezone;
5061
- const meetings = rows6.map((m) => ({ ...m, ...patched.get(String(m.id)) ?? {}, applicant: applicantOf(m) })).filter((m) => all || m.status === "scheduled");
5061
+ const meetings = rows5.map((m) => ({ ...m, ...patched.get(String(m.id)) ?? {}, applicant: applicantOf(m) })).filter((m) => all || m.status === "scheduled");
5062
5062
  return json({ meetings, adopted: decisions.length, timezone });
5063
5063
  };
5064
5064
 
@@ -5134,10 +5134,10 @@ var handleAdminPeople = async (req, url, env, ctx) => {
5134
5134
  });
5135
5135
  }
5136
5136
  }
5137
- const rows6 = [...people.values()].sort(
5137
+ const rows5 = [...people.values()].sort(
5138
5138
  (x, y) => (y.application?.createdAt ?? -1) - (x.application?.createdAt ?? -1)
5139
5139
  );
5140
- return json({ people: rows6 });
5140
+ return json({ people: rows5 });
5141
5141
  };
5142
5142
  var handleAdminPeopleAccess = async (req, url, env, ctx) => {
5143
5143
  if (req.method !== "GET" || url.pathname !== "/api/admin/people/access") return null;
@@ -6874,6 +6874,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
6874
6874
  // src/network-person-registry.ts
6875
6875
  var TOKEN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
6876
6876
  var CHAPTER_ID = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
6877
+ var FIXTURE_MARKER = /fixture|(?:^|[\s._:-])(dummy|sample|test(?:case)?)(?:$|[\s._:-])/i;
6877
6878
  function normalizeNetworkIdentity(kind, value) {
6878
6879
  const trimmed = value.trim();
6879
6880
  if (kind === "email") return trimmed.toLowerCase();
@@ -6900,6 +6901,9 @@ function validateNetworkPersonImport(input, allowedChapterIds) {
6900
6901
  if (!sourceRecordId || sourceRecordId.length > 200) errors.push("sourceRecordId is required and bounded");
6901
6902
  if (!provenance || provenance.length > 500) errors.push("provenance is required and bounded");
6902
6903
  if (!displayName || displayName.length > 200) errors.push("displayName is required and bounded");
6904
+ if (FIXTURE_MARKER.test(sourceRecordId) || FIXTURE_MARKER.test(displayName)) {
6905
+ errors.push("fixture, test, sample, and dummy rows cannot become network Persons");
6906
+ }
6903
6907
  const rawConsent = raw2.consent && typeof raw2.consent === "object" && !Array.isArray(raw2.consent) ? raw2.consent : {};
6904
6908
  const consentStates = ["unknown", "granted", "denied"];
6905
6909
  const marketing = typeof rawConsent.marketing === "string" ? rawConsent.marketing : "";
@@ -7013,7 +7017,7 @@ function canonicalNetworkPersonId(personId, persons) {
7013
7017
  }
7014
7018
  }
7015
7019
 
7016
- // src/network-person-store.ts
7020
+ // src/network-person-store-state.ts
7017
7021
  var NetworkPersonRegistryError = class extends Error {
7018
7022
  constructor(code, message) {
7019
7023
  super(message);
@@ -7021,29 +7025,112 @@ var NetworkPersonRegistryError = class extends Error {
7021
7025
  }
7022
7026
  code;
7023
7027
  };
7024
- var rows4 = (value) => Array.isArray(value) ? value : [];
7025
- async function loadNetworkPersonRegistry(db) {
7026
- const result = await db.query({
7027
- networkPersons: { $: { order: { updatedAt: "desc" }, limit: 1e3 } },
7028
- networkIdentityClaims: { $: { limit: 1e3 } },
7029
- networkPersonAliases: { $: { order: { updatedAt: "desc" }, limit: 1e3 } },
7030
- networkChapterPersonLinks: { $: { limit: 1e3 } },
7031
- networkPersonEvents: { $: { order: { createdAt: "desc" }, limit: 1e3 } }
7032
- });
7033
- return {
7034
- persons: rows4(result.networkPersons),
7035
- claims: rows4(result.networkIdentityClaims),
7036
- aliases: rows4(result.networkPersonAliases),
7037
- links: rows4(result.networkChapterPersonLinks),
7038
- events: rows4(result.networkPersonEvents)
7039
- };
7040
- }
7041
- var eventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
7028
+ var networkPersonEventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
7042
7029
  t: "update",
7043
7030
  ns: "networkPersonEvents",
7044
7031
  id: `network_person_event:${mutationId}`,
7045
7032
  attrs: { id: `network_person_event:${mutationId}`, kind, actorId: actor.id, ...ids, mutationId, detail, createdAt: now }
7046
7033
  });
7034
+ var asRows = (value) => Array.isArray(value) ? value : [];
7035
+ var MAX_ROWS = 1e4;
7036
+ async function boundedRows(db, ns, order2) {
7037
+ const collected = [];
7038
+ const pageSize = 500;
7039
+ for (let offset = 0; offset <= MAX_ROWS; offset += pageSize) {
7040
+ const result = await db.query({ [ns]: { $: { limit: pageSize, offset, ...order2 ? { order: order2 } : {} } } });
7041
+ const page = asRows(result[ns]);
7042
+ collected.push(...page);
7043
+ if (collected.length > MAX_ROWS) {
7044
+ throw new NetworkPersonRegistryError(
7045
+ "registry_too_large",
7046
+ `network Person registry ${ns} exceeds the ${MAX_ROWS.toLocaleString()}-row operational bound`
7047
+ );
7048
+ }
7049
+ if (page.length < pageSize) return collected;
7050
+ }
7051
+ return collected;
7052
+ }
7053
+ async function loadNetworkPersonRegistry(db) {
7054
+ const [persons, claims, aliases, links, events] = await Promise.all([
7055
+ boundedRows(db, "networkPersons", { updatedAt: "desc" }),
7056
+ boundedRows(db, "networkIdentityClaims"),
7057
+ boundedRows(db, "networkPersonAliases", { updatedAt: "desc" }),
7058
+ boundedRows(db, "networkChapterPersonLinks"),
7059
+ boundedRows(db, "networkPersonEvents", { createdAt: "desc" })
7060
+ ]);
7061
+ return { persons, claims, aliases, links, events };
7062
+ }
7063
+
7064
+ // src/network-person-link-store.ts
7065
+ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date.now()) {
7066
+ const state = await loadNetworkPersonRegistry(db);
7067
+ const canonicalId = canonicalNetworkPersonId(input.personId, state.persons);
7068
+ const person = state.persons.find((row) => row.id === input.personId);
7069
+ if (!person || person.state !== "active" || canonicalId !== person.id) {
7070
+ throw new NetworkPersonRegistryError("person_not_active", "link target must be an active canonical Person");
7071
+ }
7072
+ const sourceKey = await networkChapterPersonSourceKey(input.chapterId, input.chapterPersonId);
7073
+ const existingSource = state.links.find((link2) => link2.state === "shadow" && (link2.sourceKey === sourceKey || link2.chapterId === input.chapterId && link2.chapterPersonId === input.chapterPersonId));
7074
+ if (existingSource) {
7075
+ if (existingSource.personId !== person.id) {
7076
+ throw new NetworkPersonRegistryError("source_already_linked", "chapter source row is linked to another Person");
7077
+ }
7078
+ return { person, link: existingSource, replayed: true };
7079
+ }
7080
+ if (person.revision !== input.expectedPersonRevision) {
7081
+ throw new NetworkPersonRegistryError("stale_source_link", "Person changed; reload before linking this source row");
7082
+ }
7083
+ const expectedClaim = await networkIdentityClaim({ kind: "email", value: input.verifiedEmail, verifiedAt: 1 });
7084
+ if (!state.claims.some((claim) => claim.id === expectedClaim.id && claim.personId === person.id && claim.kind === "email")) {
7085
+ throw new NetworkPersonRegistryError("identity_mismatch", "source email is not an exact verified identity on this Person");
7086
+ }
7087
+ const id = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
7088
+ const previous = state.links.find((link2) => link2.id === id);
7089
+ const link = {
7090
+ id,
7091
+ personId: person.id,
7092
+ chapterId: input.chapterId,
7093
+ chapterPersonId: input.chapterPersonId,
7094
+ sourceKey,
7095
+ state: "shadow",
7096
+ createdAt: previous?.createdAt ?? now
7097
+ };
7098
+ const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
7099
+ await db.transact([
7100
+ { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
7101
+ { t: "update", ns: "networkChapterPersonLinks", id, attrs: { ...link } },
7102
+ networkPersonEventOp("chapter_source_linked", mutationId, actor, now, { chapterId: input.chapterId }, { personId: person.id })
7103
+ ], { mutationId, guards: [
7104
+ { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
7105
+ { ns: "networkChapterPersonLinks", id, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
7106
+ ] });
7107
+ return { person: nextPerson, link };
7108
+ }
7109
+ async function unlinkNetworkChapterPerson(db, input, mutationId, actor, now = Date.now()) {
7110
+ const reason = input.reason.trim();
7111
+ if (!reason || reason.length > 500) throw new NetworkPersonRegistryError("invalid_unlink", "a bounded unlink reason is required");
7112
+ const state = await loadNetworkPersonRegistry(db);
7113
+ const person = state.persons.find((row) => row.id === input.personId);
7114
+ const link = state.links.find((row) => row.id === input.linkId && row.personId === input.personId);
7115
+ if (!person || person.state !== "active" || person.revision !== input.expectedPersonRevision || !link || link.state !== "shadow") {
7116
+ throw new NetworkPersonRegistryError("stale_source_unlink", "Person or source link changed; reload before undoing the link");
7117
+ }
7118
+ const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
7119
+ const nextLink = { ...link, state: "revoked" };
7120
+ delete nextLink.sourceKey;
7121
+ await db.transact([
7122
+ { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
7123
+ { t: "update", ns: "networkChapterPersonLinks", id: link.id, attrs: { state: "revoked" } },
7124
+ { t: "retract", ns: "networkChapterPersonLinks", id: link.id, attrs: ["sourceKey"] },
7125
+ networkPersonEventOp("chapter_source_unlinked", mutationId, actor, now, { chapterId: link.chapterId, reason }, { personId: person.id })
7126
+ ], { mutationId, guards: [
7127
+ { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
7128
+ { ns: "networkChapterPersonLinks", id: link.id, exists: true, equals: { state: "shadow", personId: person.id } }
7129
+ ] });
7130
+ return { person: nextPerson, link: nextLink };
7131
+ }
7132
+
7133
+ // src/network-person-store.ts
7047
7134
  var defaultPolicy = (now, consent) => ({
7048
7135
  marketingSuppressed: consent.marketing !== "granted",
7049
7136
  discretionarySuppressed: consent.discretionary !== "granted",
@@ -7053,7 +7140,8 @@ var isGuardConflict = (error) => error instanceof Error && error.code === "trans
7053
7140
  async function linkOps(personId, links, now) {
7054
7141
  return Promise.all(links.map(async (link) => {
7055
7142
  const id = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
7056
- return { t: "update", ns: "networkChapterPersonLinks", id, attrs: { id, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, state: "shadow", createdAt: now } };
7143
+ const sourceKey = await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId);
7144
+ return { t: "update", ns: "networkChapterPersonLinks", id, attrs: { id, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
7057
7145
  }));
7058
7146
  }
7059
7147
  async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
@@ -7075,7 +7163,7 @@ async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId
7075
7163
  };
7076
7164
  await db.transact([
7077
7165
  { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...alias } },
7078
- eventOp("import_queued_ambiguous", mutationId, actor, now, { candidateCount: candidates.length, sourceSystem: input.sourceSystem }, { aliasId })
7166
+ networkPersonEventOp("import_queued_ambiguous", mutationId, actor, now, { candidateCount: candidates.length, sourceSystem: input.sourceSystem }, { aliasId })
7079
7167
  ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: false }] });
7080
7168
  return { status: "ambiguous", alias };
7081
7169
  }
@@ -7111,7 +7199,7 @@ async function importNetworkPerson(db, input, mutationId, actor, now = Date.now(
7111
7199
  { t: "update", ns: "networkPersonAliases", id: aliasId, attrs: { ...alias } },
7112
7200
  ...claimOps,
7113
7201
  ...await linkOps(personId, input.chapterLinks ?? [], now),
7114
- eventOp("person_imported", mutationId, actor, now, { sourceSystem: input.sourceSystem, identityCount: claims.length }, { personId, aliasId })
7202
+ networkPersonEventOp("person_imported", mutationId, actor, now, { sourceSystem: input.sourceSystem, identityCount: claims.length }, { personId, aliasId })
7115
7203
  ];
7116
7204
  try {
7117
7205
  await db.transact(ops, { mutationId, guards: [
@@ -7133,12 +7221,21 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
7133
7221
  const state = await loadNetworkPersonRegistry(db);
7134
7222
  const alias = state.aliases.find((row) => row.id === input.aliasId);
7135
7223
  if (!alias) throw new NetworkPersonRegistryError("alias_not_found", "source alias not found");
7136
- if (alias.revision !== input.expectedRevision || alias.status !== "ambiguous") throw new NetworkPersonRegistryError("stale_review", "source alias changed; reload before reviewing");
7224
+ const expectedStatus = input.decision === "reopen" ? "excluded" : "ambiguous";
7225
+ if (alias.revision !== input.expectedRevision || alias.status !== expectedStatus) throw new NetworkPersonRegistryError("stale_review", "source alias changed; reload before reviewing");
7226
+ if (input.decision === "reopen") {
7227
+ const next2 = { ...alias, status: "ambiguous", revision: alias.revision + 1, updatedAt: now };
7228
+ await db.transact([
7229
+ { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...next2 } },
7230
+ networkPersonEventOp("alias_reopened", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7231
+ ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "excluded" } }] });
7232
+ return next2;
7233
+ }
7137
7234
  if (input.decision === "exclude") {
7138
7235
  const next2 = { ...alias, status: "excluded", revision: alias.revision + 1, updatedAt: now };
7139
7236
  await db.transact([
7140
7237
  { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...next2 } },
7141
- eventOp("alias_excluded", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7238
+ networkPersonEventOp("alias_excluded", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7142
7239
  ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } }] });
7143
7240
  return next2;
7144
7241
  }
@@ -7154,7 +7251,7 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
7154
7251
  { t: "retract", ns: "networkPersonAliases", id: alias.id, attrs: ["candidatePersonIds"] },
7155
7252
  ...missing.map((claim) => ({ t: "update", ns: "networkIdentityClaims", id: claim.id, attrs: { ...claim, personId: canonicalId, createdAt: now } })),
7156
7253
  ...await linkOps(canonicalId, alias.chapterLinks, now),
7157
- eventOp("alias_linked", mutationId, actor, now, { identityCount: alias.identityClaims.length }, { personId: canonicalId, aliasId: alias.id })
7254
+ networkPersonEventOp("alias_linked", mutationId, actor, now, { identityCount: alias.identityClaims.length }, { personId: canonicalId, aliasId: alias.id })
7158
7255
  ], { mutationId, guards: [
7159
7256
  { ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } },
7160
7257
  ...missing.map((claim) => ({ ns: "networkIdentityClaims", id: claim.id, exists: false }))
@@ -7184,7 +7281,7 @@ async function mergeNetworkPersons(db, input, mutationId, actor, now = Date.now(
7184
7281
  await db.transact([
7185
7282
  { t: "update", ns: "networkPersons", id: source.id, attrs: { ...sourceNext } },
7186
7283
  { t: "update", ns: "networkPersons", id: target.id, attrs: { ...targetNext } },
7187
- eventOp("persons_merged", mutationId, actor, now, { targetPersonId: target.id }, { personId: source.id })
7284
+ networkPersonEventOp("persons_merged", mutationId, actor, now, { targetPersonId: target.id }, { personId: source.id })
7188
7285
  ], { mutationId, guards: [
7189
7286
  { ns: "networkPersons", id: source.id, exists: true, equals: { revision: source.revision, state: "active" } },
7190
7287
  { ns: "networkPersons", id: target.id, exists: true, equals: { revision: target.revision, state: "active" } }
@@ -7200,7 +7297,7 @@ async function unmergeNetworkPerson(db, input, mutationId, actor, now = Date.now
7200
7297
  await db.transact([
7201
7298
  { t: "update", ns: "networkPersons", id: person.id, attrs: { state: "active", revision: next.revision, updatedAt: now } },
7202
7299
  { t: "retract", ns: "networkPersons", id: person.id, attrs: ["mergedIntoId"] },
7203
- eventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
7300
+ networkPersonEventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
7204
7301
  ], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "merged", mergedIntoId: person.mergedIntoId } }] });
7205
7302
  return next;
7206
7303
  }
@@ -7214,50 +7311,257 @@ async function suppressNetworkPerson(db, input, mutationId, actor, now = Date.no
7214
7311
  const next = { ...person, contactPolicy: policy, revision: person.revision + 1, updatedAt: now };
7215
7312
  await db.transact([
7216
7313
  { t: "update", ns: "networkPersons", id: person.id, attrs: { ...next } },
7217
- eventOp("contact_suppression_changed", mutationId, actor, now, { marketingSuppressed: policy.marketingSuppressed, discretionarySuppressed: policy.discretionarySuppressed }, { personId: person.id })
7314
+ networkPersonEventOp("contact_suppression_changed", mutationId, actor, now, { marketingSuppressed: policy.marketingSuppressed, discretionarySuppressed: policy.discretionarySuppressed }, { personId: person.id })
7218
7315
  ], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision } }] });
7219
7316
  return next;
7220
7317
  }
7221
7318
 
7222
- // src/network-person-reveal.ts
7223
- async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, now = Date.now()) {
7224
- const reason = input.reason.trim();
7225
- if (!reason || reason.length > 500) {
7226
- throw new NetworkPersonRegistryError("invalid_reveal", "a bounded reveal reason is required");
7319
+ // src/network-person-bulk-preview.ts
7320
+ var NETWORK_PERSON_BULK_IMPORT_MAX_ROWS = 5e3;
7321
+ var NETWORK_PERSON_BULK_IMPORT_VERSION = 1;
7322
+ var networkPersonBulkEmptyCounts = () => ({
7323
+ new: 0,
7324
+ replay: 0,
7325
+ exactMatchReview: 0,
7326
+ invalid: 0,
7327
+ rejected: 0,
7328
+ failed: 0
7329
+ });
7330
+ var incrementNetworkPersonBulkCount = (counts, status) => {
7331
+ if (status === "exact_match_review") counts.exactMatchReview += 1;
7332
+ else counts[status] += 1;
7333
+ };
7334
+ function stableValue(value) {
7335
+ if (Array.isArray(value)) return value.map(stableValue);
7336
+ if (value && typeof value === "object") {
7337
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stableValue(item)]));
7338
+ }
7339
+ return value;
7340
+ }
7341
+ async function networkPersonBulkSha256(value) {
7342
+ const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
7343
+ return [...new Uint8Array(bytes)].map((part) => part.toString(16).padStart(2, "0")).join("");
7344
+ }
7345
+ function assertNetworkPersonBulkRows(rows5) {
7346
+ if (!Array.isArray(rows5) || rows5.length < 1) {
7347
+ throw new NetworkPersonRegistryError("bulk_rows_required", "bulk import requires at least one row");
7227
7348
  }
7349
+ if (rows5.length > NETWORK_PERSON_BULK_IMPORT_MAX_ROWS) {
7350
+ throw new NetworkPersonRegistryError(
7351
+ "bulk_rows_exceeded",
7352
+ `bulk import is limited to ${NETWORK_PERSON_BULK_IMPORT_MAX_ROWS.toLocaleString()} rows`
7353
+ );
7354
+ }
7355
+ return rows5;
7356
+ }
7357
+ async function networkPersonBulkBatchDigest(rows5) {
7358
+ const bounded = assertNetworkPersonBulkRows(rows5);
7359
+ return networkPersonBulkSha256(JSON.stringify(stableValue({ version: NETWORK_PERSON_BULK_IMPORT_VERSION, rows: bounded })));
7360
+ }
7361
+ async function networkPersonRegistryStateDigest(state) {
7362
+ const snapshot = {
7363
+ persons: state.persons.map(({ id, revision, state: personState, mergedIntoId }) => ({ id, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
7364
+ claims: state.claims.map(({ id, personId }) => ({ id, personId })).sort((a, b) => a.id.localeCompare(b.id)),
7365
+ aliases: state.aliases.map(({ id, revision, status, personId }) => ({ id, revision, status, personId })).sort((a, b) => a.id.localeCompare(b.id)),
7366
+ links: state.links.map(({ id, personId, state: linkState }) => ({ id, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
7367
+ };
7368
+ return networkPersonBulkSha256(JSON.stringify(stableValue(snapshot)));
7369
+ }
7370
+ function rejectedValidation(errors) {
7371
+ return errors.some((error) => /approved|real person|non-development|development or invalid email|fixture, test, sample/i.test(error));
7372
+ }
7373
+ async function classifyNetworkPersonBulkRows(rows5, allowedChapterIds, state) {
7374
+ const aliasIds = new Set(state.aliases.map((alias) => alias.id));
7375
+ const batchAliases = /* @__PURE__ */ new Set();
7376
+ const claimOwners = /* @__PURE__ */ new Map();
7377
+ for (const claim of state.claims) {
7378
+ const owners = claimOwners.get(claim.id) ?? /* @__PURE__ */ new Set();
7379
+ owners.add(claim.personId);
7380
+ claimOwners.set(claim.id, owners);
7381
+ }
7382
+ const results = [];
7383
+ const values = [];
7384
+ for (let index2 = 0; index2 < rows5.length; index2 += 1) {
7385
+ const validation = validateNetworkPersonImport(rows5[index2], allowedChapterIds);
7386
+ if (!validation.ok || !validation.value) {
7387
+ const errors = validation.errors.slice(0, 8);
7388
+ results.push({ rowNumber: index2 + 2, status: rejectedValidation(errors) ? "rejected" : "invalid", errors });
7389
+ values.push(null);
7390
+ continue;
7391
+ }
7392
+ const value = validation.value;
7393
+ const aliasId = await networkSourceKey(value.sourceSystem, value.sourceRecordId);
7394
+ if (batchAliases.has(aliasId)) {
7395
+ results.push({ rowNumber: index2 + 2, status: "rejected", errors: ["duplicate source record in this batch"] });
7396
+ values.push(null);
7397
+ continue;
7398
+ }
7399
+ batchAliases.add(aliasId);
7400
+ if (aliasIds.has(aliasId)) {
7401
+ results.push({ rowNumber: index2 + 2, status: "replay" });
7402
+ values.push(value);
7403
+ continue;
7404
+ }
7405
+ const claims = await Promise.all(value.identities.map(networkIdentityClaim));
7406
+ const candidates = new Set(claims.flatMap((claim) => [...claimOwners.get(claim.id) ?? []]));
7407
+ results.push(candidates.size ? { rowNumber: index2 + 2, status: "exact_match_review", candidateCount: candidates.size } : { rowNumber: index2 + 2, status: "new" });
7408
+ const plannedOwner = `bulk_row_${index2 + 2}`;
7409
+ for (const claim of claims) {
7410
+ const owners = claimOwners.get(claim.id) ?? /* @__PURE__ */ new Set();
7411
+ owners.add(plannedOwner);
7412
+ claimOwners.set(claim.id, owners);
7413
+ }
7414
+ values.push(value);
7415
+ }
7416
+ return { rows: results, values };
7417
+ }
7418
+ async function previewNetworkPersonBulkImport(db, rows5, allowedChapterIds) {
7419
+ const bounded = assertNetworkPersonBulkRows(rows5);
7228
7420
  const state = await loadNetworkPersonRegistry(db);
7229
- const target = input.kind === "person" ? state.persons.find((row) => row.id === input.id) : state.aliases.find((row) => row.id === input.id);
7230
- if (!target) throw new NetworkPersonRegistryError("reveal_not_found", "registry record not found");
7231
- const ids = input.kind === "person" ? { personId: input.id } : { aliasId: input.id };
7232
- const event = {
7233
- t: "update",
7234
- ns: "networkPersonEvents",
7235
- id: `network_person_event:${mutationId}`,
7236
- attrs: {
7237
- id: `network_person_event:${mutationId}`,
7238
- kind: "pii_revealed",
7239
- actorId: actor.id,
7240
- ...ids,
7241
- mutationId,
7242
- detail: { targetKind: input.kind, reason },
7243
- createdAt: now
7421
+ const classified = await classifyNetworkPersonBulkRows(bounded, allowedChapterIds, state);
7422
+ const counts = networkPersonBulkEmptyCounts();
7423
+ for (const row of classified.rows) incrementNetworkPersonBulkCount(counts, row.status);
7424
+ return {
7425
+ version: NETWORK_PERSON_BULK_IMPORT_VERSION,
7426
+ batchDigest: await networkPersonBulkBatchDigest(bounded),
7427
+ stateDigest: await networkPersonRegistryStateDigest(state),
7428
+ rowCount: bounded.length,
7429
+ counts,
7430
+ rows: classified.rows
7431
+ };
7432
+ }
7433
+
7434
+ // src/network-person-bulk-import.ts
7435
+ async function derivedMutationId(batchMutationId, part) {
7436
+ return `bulk_${await networkPersonBulkSha256(`${batchMutationId}\0${part}`)}`;
7437
+ }
7438
+ function matchingEvent(events, mutationId, kind) {
7439
+ return events.find((event) => event.mutationId === mutationId && event.kind === kind);
7440
+ }
7441
+ function eventString(event, key) {
7442
+ return typeof event.detail[key] === "string" ? String(event.detail[key]) : "";
7443
+ }
7444
+ function eventCounts(event) {
7445
+ const value = event.detail.counts;
7446
+ return value && typeof value === "object" && !Array.isArray(value) ? { ...networkPersonBulkEmptyCounts(), ...value } : networkPersonBulkEmptyCounts();
7447
+ }
7448
+ async function applyNetworkPersonBulkImport(db, input, batchMutationId, actor, allowedChapterIds, now = Date.now()) {
7449
+ if (input.reviewed !== true) throw new NetworkPersonRegistryError("bulk_review_required", "the exact preview must be explicitly reviewed before apply");
7450
+ const rows5 = assertNetworkPersonBulkRows(input.rows);
7451
+ const batchDigest = await networkPersonBulkBatchDigest(rows5);
7452
+ if (!/^[a-f0-9]{64}$/.test(input.expectedBatchDigest) || batchDigest !== input.expectedBatchDigest) {
7453
+ throw new NetworkPersonRegistryError("bulk_digest_changed", "bulk import rows changed after preview; preview again");
7454
+ }
7455
+ if (!/^[a-f0-9]{64}$/.test(input.expectedStateDigest)) {
7456
+ throw new NetworkPersonRegistryError("bulk_state_digest_required", "a reviewed registry state digest is required");
7457
+ }
7458
+ const startedMutationId = await derivedMutationId(batchMutationId, "started");
7459
+ const completedMutationId = await derivedMutationId(batchMutationId, "completed");
7460
+ let state = await loadNetworkPersonRegistry(db);
7461
+ const completed = matchingEvent(state.events, completedMutationId, "bulk_import_completed");
7462
+ if (completed) {
7463
+ if (eventString(completed, "batchDigest") !== batchDigest) {
7464
+ throw new NetworkPersonRegistryError("bulk_mutation_conflict", "batch mutation id was already used for different rows");
7465
+ }
7466
+ return {
7467
+ version: NETWORK_PERSON_BULK_IMPORT_VERSION,
7468
+ batchDigest,
7469
+ stateDigest: eventString(completed, "stateDigest"),
7470
+ rowCount: Number(completed.detail.rowCount) || rows5.length,
7471
+ counts: eventCounts(completed),
7472
+ rows: [],
7473
+ replayed: true
7474
+ };
7475
+ }
7476
+ let started = matchingEvent(state.events, startedMutationId, "bulk_import_started");
7477
+ if (!started) {
7478
+ const preview = await previewNetworkPersonBulkImport(db, rows5, allowedChapterIds);
7479
+ if (preview.stateDigest !== input.expectedStateDigest) {
7480
+ throw new NetworkPersonRegistryError("bulk_state_changed", "network Person registry changed after preview; preview again");
7481
+ }
7482
+ try {
7483
+ await db.transact([
7484
+ networkPersonEventOp("bulk_import_started", startedMutationId, actor, now, {
7485
+ batchDigest,
7486
+ stateDigest: input.expectedStateDigest,
7487
+ rowCount: rows5.length,
7488
+ previewCounts: preview.counts
7489
+ })
7490
+ ], {
7491
+ mutationId: startedMutationId,
7492
+ guards: [{ ns: "networkPersonEvents", id: `network_person_event:${startedMutationId}`, exists: false }]
7493
+ });
7494
+ } catch (error) {
7495
+ if (!(error instanceof Error) || error.code !== "transact_guard_failed") throw error;
7496
+ }
7497
+ state = await loadNetworkPersonRegistry(db);
7498
+ started = matchingEvent(state.events, startedMutationId, "bulk_import_started");
7499
+ }
7500
+ if (!started || eventString(started, "batchDigest") !== batchDigest || eventString(started, "stateDigest") !== input.expectedStateDigest) {
7501
+ throw new NetworkPersonRegistryError("bulk_mutation_conflict", "batch mutation id does not match the reviewed preview");
7502
+ }
7503
+ const classified = await classifyNetworkPersonBulkRows(rows5, allowedChapterIds, state);
7504
+ const results = [];
7505
+ const counts = networkPersonBulkEmptyCounts();
7506
+ for (let index2 = 0; index2 < classified.rows.length; index2 += 1) {
7507
+ const row = classified.rows[index2];
7508
+ const value = classified.values[index2];
7509
+ if (!value) {
7510
+ results.push(row);
7511
+ incrementNetworkPersonBulkCount(counts, row.status);
7512
+ continue;
7513
+ }
7514
+ try {
7515
+ const result = await importNetworkPerson(
7516
+ db,
7517
+ value,
7518
+ await derivedMutationId(batchMutationId, `row_${index2 + 2}`),
7519
+ actor,
7520
+ now + index2 + 1
7521
+ );
7522
+ const status = "replayed" in result && result.replayed ? "replay" : result.status === "ambiguous" ? "exact_match_review" : "new";
7523
+ const applied = {
7524
+ rowNumber: index2 + 2,
7525
+ status,
7526
+ ...result.status === "ambiguous" ? { candidateCount: result.alias.candidatePersonIds?.length ?? 0 } : {}
7527
+ };
7528
+ results.push(applied);
7529
+ incrementNetworkPersonBulkCount(counts, status);
7530
+ } catch (error) {
7531
+ const failed = {
7532
+ rowNumber: index2 + 2,
7533
+ status: "failed",
7534
+ errors: [error instanceof Error ? error.message : "row failed"]
7535
+ };
7536
+ results.push(failed);
7537
+ incrementNetworkPersonBulkCount(counts, failed.status);
7244
7538
  }
7539
+ }
7540
+ if (counts.failed === 0) {
7541
+ await db.transact([
7542
+ networkPersonEventOp("bulk_import_completed", completedMutationId, actor, now + rows5.length + 1, {
7543
+ batchDigest,
7544
+ stateDigest: input.expectedStateDigest,
7545
+ rowCount: rows5.length,
7546
+ counts
7547
+ })
7548
+ ], { mutationId: completedMutationId });
7549
+ }
7550
+ return {
7551
+ version: NETWORK_PERSON_BULK_IMPORT_VERSION,
7552
+ batchDigest,
7553
+ stateDigest: input.expectedStateDigest,
7554
+ rowCount: rows5.length,
7555
+ counts,
7556
+ rows: results
7245
7557
  };
7246
- await db.transact([event], {
7247
- mutationId,
7248
- guards: [{ ns: "networkPersonEvents", id: `network_person_event:${mutationId}`, exists: false }]
7249
- });
7250
- return input.kind === "person" ? { kind: "person", person: target, identities: state.claims.filter((claim) => claim.personId === input.id) } : { kind: "alias", alias: target };
7251
7558
  }
7252
7559
 
7253
- // src/worker-routes-network-person-registry.ts
7254
- var PATH2 = "/api/admin/network-person-registry";
7255
- var MAX_BODY2 = 32e3;
7256
- var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
7560
+ // src/network-person-route-projection.ts
7257
7561
  function redactedName(displayName) {
7258
7562
  return displayName.split(/\s+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}\u2026`).join(" ");
7259
7563
  }
7260
- var redactedLink = async (link) => ({
7564
+ var redactedNetworkPersonLink = async (link) => ({
7261
7565
  id: link.id,
7262
7566
  personId: link.personId,
7263
7567
  chapterId: link.chapterId,
@@ -7265,13 +7569,13 @@ var redactedLink = async (link) => ({
7265
7569
  state: link.state,
7266
7570
  createdAt: link.createdAt
7267
7571
  });
7268
- var safePolicy = (policy) => ({
7572
+ var safeNetworkContactPolicy = (policy) => ({
7269
7573
  marketingSuppressed: policy.marketingSuppressed,
7270
7574
  discretionarySuppressed: policy.discretionarySuppressed,
7271
7575
  hasReason: Boolean(policy.reason),
7272
7576
  updatedAt: policy.updatedAt
7273
7577
  });
7274
- var safeAlias = (alias) => ({
7578
+ var safeNetworkPersonAlias = async (alias) => ({
7275
7579
  id: alias.id,
7276
7580
  sourceSystem: alias.sourceSystem,
7277
7581
  displayNameHint: redactedName(alias.displayName),
@@ -7286,17 +7590,91 @@ var safeAlias = (alias) => ({
7286
7590
  },
7287
7591
  identityClaims: alias.identityClaims.map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7288
7592
  chapterIds: [...new Set(alias.chapterLinks.map((link) => link.chapterId))],
7593
+ chapterSources: await Promise.all(alias.chapterLinks.map(async (link) => ({
7594
+ chapterId: link.chapterId,
7595
+ sourceKey: await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId)
7596
+ }))),
7289
7597
  revision: alias.revision,
7290
7598
  updatedAt: alias.updatedAt
7291
7599
  });
7292
- var safePerson = (person) => ({
7600
+ var safeNetworkPerson = (person, claims = []) => ({
7293
7601
  id: person.id,
7602
+ displayName: person.displayName,
7294
7603
  displayNameHint: redactedName(person.displayName),
7604
+ verifiedEmails: claims.filter((claim) => claim.kind === "email").map((claim) => ({ value: claim.value, verifiedAt: claim.verifiedAt })).sort((left, right) => right.verifiedAt - left.verifiedAt || left.value.localeCompare(right.value)),
7295
7605
  state: person.state,
7296
7606
  mergedIntoId: person.mergedIntoId ?? null,
7297
7607
  revision: person.revision,
7298
- contactPolicy: safePolicy(person.contactPolicy)
7608
+ contactPolicy: safeNetworkContactPolicy(person.contactPolicy)
7299
7609
  });
7610
+ async function projectNetworkPersonRegistryState(state) {
7611
+ const persons = await Promise.all(state.persons.map(async (person) => {
7612
+ const claims = state.claims.filter((claim) => claim.personId === person.id);
7613
+ const links = state.links.filter((link) => link.personId === person.id && link.state === "shadow");
7614
+ const safeLinks = await Promise.all(links.map(redactedNetworkPersonLink));
7615
+ const canonicalId = canonicalNetworkPersonId(person.id, state.persons);
7616
+ const canonical2 = state.persons.find((candidate) => candidate.id === canonicalId) ?? person;
7617
+ return {
7618
+ ...safeNetworkPerson(person, claims),
7619
+ canonicalPersonId: canonicalId,
7620
+ identityClaims: claims.map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7621
+ chapterLinks: safeLinks,
7622
+ chapterProjections: projectNetworkContactPolicy(canonical2.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safeNetworkContactPolicy(contactPolicy) }))
7623
+ };
7624
+ }));
7625
+ const aliases = await Promise.all(state.aliases.map(safeNetworkPersonAlias));
7626
+ return {
7627
+ persons,
7628
+ aliases,
7629
+ reviewQueue: aliases.filter((alias) => alias.status === "ambiguous"),
7630
+ excluded: aliases.filter((alias) => alias.status === "excluded"),
7631
+ recentEvents: state.events.slice(0, 100).map((event) => ({
7632
+ id: event.id,
7633
+ kind: event.kind,
7634
+ personId: event.personId ?? null,
7635
+ aliasId: event.aliasId ?? null,
7636
+ actorId: event.actorId,
7637
+ mutationId: event.mutationId,
7638
+ createdAt: event.createdAt
7639
+ }))
7640
+ };
7641
+ }
7642
+
7643
+ // src/network-person-reveal.ts
7644
+ async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, now = Date.now()) {
7645
+ const reason = input.reason.trim();
7646
+ if (!reason || reason.length > 500) {
7647
+ throw new NetworkPersonRegistryError("invalid_reveal", "a bounded reveal reason is required");
7648
+ }
7649
+ const state = await loadNetworkPersonRegistry(db);
7650
+ const target = input.kind === "person" ? state.persons.find((row) => row.id === input.id) : state.aliases.find((row) => row.id === input.id);
7651
+ if (!target) throw new NetworkPersonRegistryError("reveal_not_found", "registry record not found");
7652
+ const ids = input.kind === "person" ? { personId: input.id } : { aliasId: input.id };
7653
+ const event = {
7654
+ t: "update",
7655
+ ns: "networkPersonEvents",
7656
+ id: `network_person_event:${mutationId}`,
7657
+ attrs: {
7658
+ id: `network_person_event:${mutationId}`,
7659
+ kind: "pii_revealed",
7660
+ actorId: actor.id,
7661
+ ...ids,
7662
+ mutationId,
7663
+ detail: { targetKind: input.kind, reason },
7664
+ createdAt: now
7665
+ }
7666
+ };
7667
+ await db.transact([event], {
7668
+ mutationId,
7669
+ guards: [{ ns: "networkPersonEvents", id: `network_person_event:${mutationId}`, exists: false }]
7670
+ });
7671
+ return input.kind === "person" ? { kind: "person", person: target, identities: state.claims.filter((claim) => claim.personId === input.id) } : { kind: "alias", alias: target };
7672
+ }
7673
+
7674
+ // src/worker-routes-network-person-registry.ts
7675
+ var PATH2 = "/api/admin/network-person-registry";
7676
+ var MAX_BODY2 = 8 * 1024 * 1024;
7677
+ var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
7300
7678
  var positiveRevision = (value) => {
7301
7679
  const parsed = Number(value);
7302
7680
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
@@ -7310,7 +7688,7 @@ async function gate10(req, env, ctx) {
7310
7688
  }
7311
7689
  function registryError(error) {
7312
7690
  if (error instanceof NetworkPersonRegistryError) {
7313
- const status = error.code.includes("stale") || error.code === "identity_conflict" ? 409 : error.code.endsWith("not_found") ? 404 : 400;
7691
+ const status = error.code.includes("stale") || error.code === "identity_conflict" || error.code === "bulk_state_changed" || error.code === "bulk_digest_changed" || error.code === "bulk_mutation_conflict" ? 409 : error.code.endsWith("not_found") ? 404 : 400;
7314
7692
  return json({ error: error.message, code: error.code }, status);
7315
7693
  }
7316
7694
  if (error instanceof Error && error.code === "transact_guard_failed") {
@@ -7345,6 +7723,23 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7345
7723
  if (!MUTATION_ID4.test(mutationId)) return json({ error: "a valid mutationId is required" }, 400);
7346
7724
  const actionActor = { id: got.user.userId };
7347
7725
  try {
7726
+ if (command === "preview-bulk-import") {
7727
+ const result = await previewNetworkPersonBulkImport(
7728
+ db,
7729
+ input.rows,
7730
+ ctx.chapter.networkPersonRegistry.chapterIds
7731
+ );
7732
+ return json({ result });
7733
+ }
7734
+ if (command === "apply-bulk-import") {
7735
+ const result = await applyNetworkPersonBulkImport(db, {
7736
+ rows: input.rows,
7737
+ expectedBatchDigest: typeof input.expectedBatchDigest === "string" ? input.expectedBatchDigest : "",
7738
+ expectedStateDigest: typeof input.expectedStateDigest === "string" ? input.expectedStateDigest : "",
7739
+ reviewed: input.reviewed === true
7740
+ }, mutationId, actionActor, ctx.chapter.networkPersonRegistry.chapterIds);
7741
+ return json({ result }, result.replayed ? 200 : 201);
7742
+ }
7348
7743
  if (command === "import") {
7349
7744
  const validated = validateNetworkPersonImport(input, ctx.chapter.networkPersonRegistry.chapterIds);
7350
7745
  if (!validated.ok || !validated.value) return json({ error: "invalid import", details: validated.errors }, 400);
@@ -7352,14 +7747,14 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7352
7747
  return json({
7353
7748
  result: {
7354
7749
  status: result.status,
7355
- alias: safeAlias(result.alias),
7356
- ..."person" in result && result.person ? { person: safePerson(result.person) } : {},
7750
+ alias: await safeNetworkPersonAlias(result.alias),
7751
+ ..."person" in result && result.person ? { person: safeNetworkPerson(result.person, result.alias.identityClaims) } : {},
7357
7752
  ..."replayed" in result ? { replayed: result.replayed } : {}
7358
7753
  }
7359
7754
  }, 201);
7360
7755
  }
7361
7756
  if (command === "review") {
7362
- if (input.decision !== "link" && input.decision !== "exclude") return json({ error: "review decision must be link or exclude" }, 400);
7757
+ if (input.decision !== "link" && input.decision !== "exclude" && input.decision !== "reopen") return json({ error: "review decision must be link, exclude, or reopen" }, 400);
7363
7758
  const expectedRevision = positiveRevision(input.expectedRevision);
7364
7759
  if (!expectedRevision) return json({ error: "expectedRevision is required" }, 400);
7365
7760
  if (input.decision === "link" && typeof input.personId !== "string") return json({ error: "personId is required when linking" }, 400);
@@ -7369,7 +7764,42 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7369
7764
  decision: input.decision,
7370
7765
  ...typeof input.personId === "string" ? { personId: input.personId } : {}
7371
7766
  }, mutationId, actionActor);
7372
- return json({ result: safeAlias(result) });
7767
+ return json({ result: await safeNetworkPersonAlias(result) });
7768
+ }
7769
+ if (command === "link-source") {
7770
+ const expectedPersonRevision = positiveRevision(input.expectedPersonRevision);
7771
+ const chapterId = typeof input.chapterId === "string" ? input.chapterId : "";
7772
+ const chapterPersonId = typeof input.chapterPersonId === "string" ? input.chapterPersonId.trim() : "";
7773
+ const verifiedEmail = typeof input.verifiedEmail === "string" ? input.verifiedEmail.trim() : "";
7774
+ if (!expectedPersonRevision || !ctx.chapter.networkPersonRegistry.chapterIds.includes(chapterId) || !chapterPersonId || chapterPersonId.length > 200 || !verifiedEmail || verifiedEmail.length > 320) return json({ error: "a configured source row, exact email, and Person revision are required" }, 400);
7775
+ const result = await linkNetworkChapterPerson(db, {
7776
+ personId: typeof input.personId === "string" ? input.personId : "",
7777
+ expectedPersonRevision,
7778
+ chapterId,
7779
+ chapterPersonId,
7780
+ verifiedEmail
7781
+ }, mutationId, actionActor);
7782
+ return json({
7783
+ result: {
7784
+ person: safeNetworkPerson(result.person),
7785
+ link: await redactedNetworkPersonLink(result.link),
7786
+ ...result.replayed ? { replayed: true } : {}
7787
+ }
7788
+ });
7789
+ }
7790
+ if (command === "unlink-source") {
7791
+ const expectedPersonRevision = positiveRevision(input.expectedPersonRevision);
7792
+ const reason = typeof input.reason === "string" ? input.reason.trim() : "";
7793
+ if (!expectedPersonRevision || !reason || reason.length > 500) {
7794
+ return json({ error: "a Person revision and bounded unlink reason are required" }, 400);
7795
+ }
7796
+ const result = await unlinkNetworkChapterPerson(db, {
7797
+ personId: typeof input.personId === "string" ? input.personId : "",
7798
+ linkId: typeof input.linkId === "string" ? input.linkId : "",
7799
+ expectedPersonRevision,
7800
+ reason
7801
+ }, mutationId, actionActor);
7802
+ return json({ result: { person: safeNetworkPerson(result.person), linkState: result.link.state } });
7373
7803
  }
7374
7804
  if (command === "merge") {
7375
7805
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7381,7 +7811,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7381
7811
  expectedRevision,
7382
7812
  expectedTargetRevision
7383
7813
  }, mutationId, actionActor);
7384
- return json({ result: { source: safePerson(result.source), target: safePerson(result.target) } });
7814
+ return json({ result: { source: safeNetworkPerson(result.source), target: safeNetworkPerson(result.target) } });
7385
7815
  }
7386
7816
  if (command === "unmerge") {
7387
7817
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7390,7 +7820,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7390
7820
  personId: typeof input.personId === "string" ? input.personId : "",
7391
7821
  expectedRevision
7392
7822
  }, mutationId, actionActor);
7393
- return json({ result: safePerson(result) });
7823
+ return json({ result: safeNetworkPerson(result) });
7394
7824
  }
7395
7825
  if (command === "suppression") {
7396
7826
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7406,7 +7836,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7406
7836
  discretionarySuppressed: input.discretionarySuppressed,
7407
7837
  ...reason ? { reason } : {}
7408
7838
  }, mutationId, actionActor);
7409
- return json({ result: safePerson(result) });
7839
+ return json({ result: safeNetworkPerson(result) });
7410
7840
  }
7411
7841
  if (command === "reveal") {
7412
7842
  if (input.kind !== "person" && input.kind !== "alias") return json({ error: "reveal kind must be person or alias" }, 400);
@@ -7425,48 +7855,19 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7425
7855
  }
7426
7856
  }
7427
7857
  const state = await loadNetworkPersonRegistry(db);
7428
- const persons = await Promise.all(state.persons.map(async (person) => {
7429
- const links = state.links.filter((link) => link.personId === person.id);
7430
- const safeLinks = await Promise.all(links.map(redactedLink));
7431
- const canonicalId = canonicalNetworkPersonId(person.id, state.persons);
7432
- const canonical2 = state.persons.find((candidate) => candidate.id === canonicalId) ?? person;
7433
- return {
7434
- id: person.id,
7435
- displayNameHint: redactedName(person.displayName),
7436
- state: person.state,
7437
- canonicalPersonId: canonicalId,
7438
- mergedIntoId: person.mergedIntoId ?? null,
7439
- revision: person.revision,
7440
- contactPolicy: safePolicy(person.contactPolicy),
7441
- identityClaims: state.claims.filter((claim) => claim.personId === person.id).map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7442
- chapterLinks: safeLinks,
7443
- chapterProjections: projectNetworkContactPolicy(canonical2.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safePolicy(contactPolicy) }))
7444
- };
7445
- }));
7446
- const aliases = state.aliases.map(safeAlias);
7858
+ const projection = await projectNetworkPersonRegistryState(state);
7447
7859
  return json({
7448
7860
  version: 1,
7449
7861
  mode: "shadow",
7450
7862
  writable: true,
7451
7863
  authoritativeForChapterOperation: false,
7452
- // loadNetworkPersonRegistry deliberately bounds each collection at 1,000.
7453
- // Exactly 1,000 links may mean there are more, so fail closed rather than
7454
- // letting a consumer mistake an omitted explicit link for "source only".
7455
- chapterLinkProjectionComplete: state.links.length < 1e3,
7864
+ // The store pages every collection to its explicit 10,000-row operational
7865
+ // bound and rejects larger state instead of returning a partial projection.
7866
+ chapterLinkProjectionComplete: true,
7867
+ personProjectionComplete: true,
7868
+ aliasProjectionComplete: true,
7456
7869
  configuredChapterIds: ctx.chapter.networkPersonRegistry.chapterIds,
7457
- persons,
7458
- aliases,
7459
- reviewQueue: aliases.filter((alias) => alias.status === "ambiguous"),
7460
- excluded: aliases.filter((alias) => alias.status === "excluded"),
7461
- recentEvents: state.events.slice(0, 100).map((event) => ({
7462
- id: event.id,
7463
- kind: event.kind,
7464
- personId: event.personId ?? null,
7465
- aliasId: event.aliasId ?? null,
7466
- actorId: event.actorId,
7467
- mutationId: event.mutationId,
7468
- createdAt: event.createdAt
7469
- }))
7870
+ ...projection
7470
7871
  });
7471
7872
  };
7472
7873
 
@@ -7659,12 +8060,12 @@ function projectNetworkCommercialEvents(input, sources) {
7659
8060
  const prior = latest.get(key);
7660
8061
  if (!prior || order(prior, fact) <= 0) latest.set(key, row);
7661
8062
  }
7662
- const rows6 = [...latest.values()];
7663
- const customers = rows6.filter((row) => row.objectKind === "customer");
7664
- const subscriptions = rows6.filter((row) => row.objectKind === "subscription");
7665
- const invoices = rows6.filter((row) => row.objectKind === "invoice");
7666
- const refunds = rows6.filter((row) => row.objectKind === "refund" && row.eventType.startsWith("refund."));
7667
- for (const charge of rows6.filter((row) => row.eventType === "charge.refunded")) {
8063
+ const rows5 = [...latest.values()];
8064
+ const customers = rows5.filter((row) => row.objectKind === "customer");
8065
+ const subscriptions = rows5.filter((row) => row.objectKind === "subscription");
8066
+ const invoices = rows5.filter((row) => row.objectKind === "invoice");
8067
+ const refunds = rows5.filter((row) => row.objectKind === "refund" && row.eventType.startsWith("refund."));
8068
+ for (const charge of rows5.filter((row) => row.eventType === "charge.refunded")) {
7668
8069
  if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
7669
8070
  exceptions.push({ eventId: charge.eventId, kind: "charge_refund_without_refund_object", sourceKey: charge.sourceKey, detail: { eventType: charge.eventType, objectId: charge.objectId } });
7670
8071
  }
@@ -7693,7 +8094,7 @@ var materializedNamespaces = [
7693
8094
  "networkCommercialInvoices",
7694
8095
  "networkCommercialRefunds"
7695
8096
  ];
7696
- async function rows5(db, ns) {
8097
+ async function rows4(db, ns) {
7697
8098
  const all = [];
7698
8099
  const pageSize = 500;
7699
8100
  for (let offset = 0; ; offset += pageSize) {
@@ -7704,9 +8105,9 @@ async function rows5(db, ns) {
7704
8105
  }
7705
8106
  }
7706
8107
  async function loadNetworkCommercialProjection(db, sources) {
7707
- const stored = await rows5(db, "networkCommercialEvents");
8108
+ const stored = await rows4(db, "networkCommercialEvents");
7708
8109
  const projection = projectNetworkCommercialEvents(stored.map((row) => row.fact), sources);
7709
- const persisted = await rows5(db, "networkCommercialExceptions");
8110
+ const persisted = await rows4(db, "networkCommercialExceptions");
7710
8111
  for (const row of persisted) {
7711
8112
  if (row.status !== "active" || row.kind !== "collision") continue;
7712
8113
  if (projection.exceptions.some((item) => item.eventId === row.eventId && item.kind === "collision")) continue;
@@ -7744,8 +8145,8 @@ function projectionOps(projection, now) {
7744
8145
  async function rebuildNetworkCommercialProjection(db, sources, mutationId, now = Date.now()) {
7745
8146
  const projection = await loadNetworkCommercialProjection(db, sources);
7746
8147
  const [existing, ...materialized] = await Promise.all([
7747
- rows5(db, "networkCommercialExceptions"),
7748
- ...materializedNamespaces.map((ns) => rows5(db, ns))
8148
+ rows4(db, "networkCommercialExceptions"),
8149
+ ...materializedNamespaces.map((ns) => rows4(db, ns))
7749
8150
  ]);
7750
8151
  const activeIds = new Set(projection.exceptions.map((item) => exceptionId(item.eventId, item.kind)));
7751
8152
  const projectedIds = new Set(projectionOps(projection, now).map((op) => String(op.id)));
@@ -7854,11 +8255,11 @@ async function listStripeObjects(client, path, params = {}, maxPages = 20) {
7854
8255
  if (!Array.isArray(page.data) || typeof page.has_more !== "boolean") {
7855
8256
  throw new Error("Stripe returned an invalid commercial list response");
7856
8257
  }
7857
- const rows6 = page.data.map(asRaw).filter((row) => row !== null);
7858
- if (rows6.length !== page.data.length) throw new Error("Stripe returned a malformed commercial object");
7859
- objects.push(...rows6);
8258
+ const rows5 = page.data.map(asRaw).filter((row) => row !== null);
8259
+ if (rows5.length !== page.data.length) throw new Error("Stripe returned a malformed commercial object");
8260
+ objects.push(...rows5);
7860
8261
  if (!page.has_more) return { objects, complete: true };
7861
- const cursor = rows6.at(-1)?.id;
8262
+ const cursor = rows5.at(-1)?.id;
7862
8263
  if (typeof cursor !== "string" || !cursor) throw new Error("Stripe pagination did not provide a cursor");
7863
8264
  startingAfter = cursor;
7864
8265
  }
@@ -8110,14 +8511,14 @@ var PAGE2 = 500;
8110
8511
  var MAX_SCAN = 5e3;
8111
8512
  var MAX_RECORDS2 = 500;
8112
8513
  async function localCommercialRows(db) {
8113
- const rows6 = [];
8514
+ const rows5 = [];
8114
8515
  for (let offset = 0; offset < MAX_SCAN; offset += PAGE2) {
8115
8516
  const result = await db.query({ applications: { $: { limit: PAGE2, offset, order: { createdAt: "asc" } } } });
8116
8517
  const page = Array.isArray(result.applications) ? result.applications : [];
8117
- rows6.push(...page.slice(0, MAX_SCAN - rows6.length));
8118
- if (page.length < PAGE2) return { rows: rows6, truncated: false };
8518
+ rows5.push(...page.slice(0, MAX_SCAN - rows5.length));
8519
+ if (page.length < PAGE2) return { rows: rows5, truncated: false };
8119
8520
  }
8120
- return { rows: rows6, truncated: true };
8521
+ return { rows: rows5, truncated: true };
8121
8522
  }
8122
8523
  var handleNetworkCommercialParity = async (req, url, env, ctx) => {
8123
8524
  if (req.method !== "GET" || url.pathname !== PARITY_PATH) return null;
@@ -8676,17 +9077,17 @@ async function builderLibraryDigest(document) {
8676
9077
  var control2 = (row) => row?.document && typeof row.document === "object" ? row : null;
8677
9078
  var local2 = (row) => row?.document && typeof row.document === "object" ? row : null;
8678
9079
  async function loadActiveBuilderLibraryControls(db, targetId, environment) {
8679
- const rows6 = (await db.query({ builderLibraryControls: { $: { where: { targetId, environment, active: true }, limit: 500 } } })).builderLibraryControls;
8680
- return Array.isArray(rows6) ? rows6.map(control2).filter(Boolean) : [];
9080
+ const rows5 = (await db.query({ builderLibraryControls: { $: { where: { targetId, environment, active: true }, limit: 500 } } })).builderLibraryControls;
9081
+ return Array.isArray(rows5) ? rows5.map(control2).filter(Boolean) : [];
8681
9082
  }
8682
9083
  async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environment) {
8683
- const rows6 = (await db.query({ builderLibraryControls: { $: { where: { sourceId, targetId, environment }, order: { revision: "desc" }, limit: 20 } } })).builderLibraryControls;
8684
- return control2(Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
9084
+ const rows5 = (await db.query({ builderLibraryControls: { $: { where: { sourceId, targetId, environment }, order: { revision: "desc" }, limit: 20 } } })).builderLibraryControls;
9085
+ return control2(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
8685
9086
  }
8686
9087
  async function loadLocalBuilderLibrary(db, appId, environment) {
8687
9088
  const id = `builder-library-local:${environment}`;
8688
- const rows6 = (await db.query({ builderLibraryLocal: { $: { where: { id }, limit: 1 } } })).builderLibraryLocal;
8689
- return local2(Array.isArray(rows6) ? rows6[0] : void 0);
9089
+ const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id }, limit: 1 } } })).builderLibraryLocal;
9090
+ return local2(Array.isArray(rows5) ? rows5[0] : void 0);
8690
9091
  }
8691
9092
  async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
8692
9093
  const { document, digest: digest2 } = envelope;