@odla-ai/chapter 0.47.4 → 0.47.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -7013,7 +7013,7 @@ function canonicalNetworkPersonId(personId, persons) {
7013
7013
  }
7014
7014
  }
7015
7015
 
7016
- // src/network-person-store.ts
7016
+ // src/network-person-store-state.ts
7017
7017
  var NetworkPersonRegistryError = class extends Error {
7018
7018
  constructor(code, message) {
7019
7019
  super(message);
@@ -7021,29 +7021,112 @@ var NetworkPersonRegistryError = class extends Error {
7021
7021
  }
7022
7022
  code;
7023
7023
  };
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 = {}) => ({
7024
+ var networkPersonEventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
7042
7025
  t: "update",
7043
7026
  ns: "networkPersonEvents",
7044
7027
  id: `network_person_event:${mutationId}`,
7045
7028
  attrs: { id: `network_person_event:${mutationId}`, kind, actorId: actor.id, ...ids, mutationId, detail, createdAt: now }
7046
7029
  });
7030
+ var asRows = (value) => Array.isArray(value) ? value : [];
7031
+ var MAX_ROWS = 1e4;
7032
+ async function boundedRows(db, ns, order2) {
7033
+ const collected = [];
7034
+ const pageSize = 500;
7035
+ for (let offset = 0; offset <= MAX_ROWS; offset += pageSize) {
7036
+ const result = await db.query({ [ns]: { $: { limit: pageSize, offset, ...order2 ? { order: order2 } : {} } } });
7037
+ const page = asRows(result[ns]);
7038
+ collected.push(...page);
7039
+ if (collected.length > MAX_ROWS) {
7040
+ throw new NetworkPersonRegistryError(
7041
+ "registry_too_large",
7042
+ `network Person registry ${ns} exceeds the ${MAX_ROWS.toLocaleString()}-row operational bound`
7043
+ );
7044
+ }
7045
+ if (page.length < pageSize) return collected;
7046
+ }
7047
+ return collected;
7048
+ }
7049
+ async function loadNetworkPersonRegistry(db) {
7050
+ const [persons, claims, aliases, links, events] = await Promise.all([
7051
+ boundedRows(db, "networkPersons", { updatedAt: "desc" }),
7052
+ boundedRows(db, "networkIdentityClaims"),
7053
+ boundedRows(db, "networkPersonAliases", { updatedAt: "desc" }),
7054
+ boundedRows(db, "networkChapterPersonLinks"),
7055
+ boundedRows(db, "networkPersonEvents", { createdAt: "desc" })
7056
+ ]);
7057
+ return { persons, claims, aliases, links, events };
7058
+ }
7059
+
7060
+ // src/network-person-link-store.ts
7061
+ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date.now()) {
7062
+ const state = await loadNetworkPersonRegistry(db);
7063
+ const canonicalId = canonicalNetworkPersonId(input.personId, state.persons);
7064
+ const person = state.persons.find((row) => row.id === input.personId);
7065
+ if (!person || person.state !== "active" || canonicalId !== person.id) {
7066
+ throw new NetworkPersonRegistryError("person_not_active", "link target must be an active canonical Person");
7067
+ }
7068
+ const sourceKey = await networkChapterPersonSourceKey(input.chapterId, input.chapterPersonId);
7069
+ const existingSource = state.links.find((link2) => link2.state === "shadow" && (link2.sourceKey === sourceKey || link2.chapterId === input.chapterId && link2.chapterPersonId === input.chapterPersonId));
7070
+ if (existingSource) {
7071
+ if (existingSource.personId !== person.id) {
7072
+ throw new NetworkPersonRegistryError("source_already_linked", "chapter source row is linked to another Person");
7073
+ }
7074
+ return { person, link: existingSource, replayed: true };
7075
+ }
7076
+ if (person.revision !== input.expectedPersonRevision) {
7077
+ throw new NetworkPersonRegistryError("stale_source_link", "Person changed; reload before linking this source row");
7078
+ }
7079
+ const expectedClaim = await networkIdentityClaim({ kind: "email", value: input.verifiedEmail, verifiedAt: 1 });
7080
+ if (!state.claims.some((claim) => claim.id === expectedClaim.id && claim.personId === person.id && claim.kind === "email")) {
7081
+ throw new NetworkPersonRegistryError("identity_mismatch", "source email is not an exact verified identity on this Person");
7082
+ }
7083
+ const id = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
7084
+ const previous = state.links.find((link2) => link2.id === id);
7085
+ const link = {
7086
+ id,
7087
+ personId: person.id,
7088
+ chapterId: input.chapterId,
7089
+ chapterPersonId: input.chapterPersonId,
7090
+ sourceKey,
7091
+ state: "shadow",
7092
+ createdAt: previous?.createdAt ?? now
7093
+ };
7094
+ const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
7095
+ await db.transact([
7096
+ { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
7097
+ { t: "update", ns: "networkChapterPersonLinks", id, attrs: { ...link } },
7098
+ networkPersonEventOp("chapter_source_linked", mutationId, actor, now, { chapterId: input.chapterId }, { personId: person.id })
7099
+ ], { mutationId, guards: [
7100
+ { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
7101
+ { ns: "networkChapterPersonLinks", id, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
7102
+ ] });
7103
+ return { person: nextPerson, link };
7104
+ }
7105
+ async function unlinkNetworkChapterPerson(db, input, mutationId, actor, now = Date.now()) {
7106
+ const reason = input.reason.trim();
7107
+ if (!reason || reason.length > 500) throw new NetworkPersonRegistryError("invalid_unlink", "a bounded unlink reason is required");
7108
+ const state = await loadNetworkPersonRegistry(db);
7109
+ const person = state.persons.find((row) => row.id === input.personId);
7110
+ const link = state.links.find((row) => row.id === input.linkId && row.personId === input.personId);
7111
+ if (!person || person.state !== "active" || person.revision !== input.expectedPersonRevision || !link || link.state !== "shadow") {
7112
+ throw new NetworkPersonRegistryError("stale_source_unlink", "Person or source link changed; reload before undoing the link");
7113
+ }
7114
+ const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
7115
+ const nextLink = { ...link, state: "revoked" };
7116
+ delete nextLink.sourceKey;
7117
+ await db.transact([
7118
+ { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
7119
+ { t: "update", ns: "networkChapterPersonLinks", id: link.id, attrs: { state: "revoked" } },
7120
+ { t: "retract", ns: "networkChapterPersonLinks", id: link.id, attrs: ["sourceKey"] },
7121
+ networkPersonEventOp("chapter_source_unlinked", mutationId, actor, now, { chapterId: link.chapterId, reason }, { personId: person.id })
7122
+ ], { mutationId, guards: [
7123
+ { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
7124
+ { ns: "networkChapterPersonLinks", id: link.id, exists: true, equals: { state: "shadow", personId: person.id } }
7125
+ ] });
7126
+ return { person: nextPerson, link: nextLink };
7127
+ }
7128
+
7129
+ // src/network-person-store.ts
7047
7130
  var defaultPolicy = (now, consent) => ({
7048
7131
  marketingSuppressed: consent.marketing !== "granted",
7049
7132
  discretionarySuppressed: consent.discretionary !== "granted",
@@ -7053,7 +7136,8 @@ var isGuardConflict = (error) => error instanceof Error && error.code === "trans
7053
7136
  async function linkOps(personId, links, now) {
7054
7137
  return Promise.all(links.map(async (link) => {
7055
7138
  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 } };
7139
+ const sourceKey = await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId);
7140
+ return { t: "update", ns: "networkChapterPersonLinks", id, attrs: { id, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
7057
7141
  }));
7058
7142
  }
7059
7143
  async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
@@ -7075,7 +7159,7 @@ async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId
7075
7159
  };
7076
7160
  await db.transact([
7077
7161
  { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...alias } },
7078
- eventOp("import_queued_ambiguous", mutationId, actor, now, { candidateCount: candidates.length, sourceSystem: input.sourceSystem }, { aliasId })
7162
+ networkPersonEventOp("import_queued_ambiguous", mutationId, actor, now, { candidateCount: candidates.length, sourceSystem: input.sourceSystem }, { aliasId })
7079
7163
  ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: false }] });
7080
7164
  return { status: "ambiguous", alias };
7081
7165
  }
@@ -7111,7 +7195,7 @@ async function importNetworkPerson(db, input, mutationId, actor, now = Date.now(
7111
7195
  { t: "update", ns: "networkPersonAliases", id: aliasId, attrs: { ...alias } },
7112
7196
  ...claimOps,
7113
7197
  ...await linkOps(personId, input.chapterLinks ?? [], now),
7114
- eventOp("person_imported", mutationId, actor, now, { sourceSystem: input.sourceSystem, identityCount: claims.length }, { personId, aliasId })
7198
+ networkPersonEventOp("person_imported", mutationId, actor, now, { sourceSystem: input.sourceSystem, identityCount: claims.length }, { personId, aliasId })
7115
7199
  ];
7116
7200
  try {
7117
7201
  await db.transact(ops, { mutationId, guards: [
@@ -7133,12 +7217,21 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
7133
7217
  const state = await loadNetworkPersonRegistry(db);
7134
7218
  const alias = state.aliases.find((row) => row.id === input.aliasId);
7135
7219
  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");
7220
+ const expectedStatus = input.decision === "reopen" ? "excluded" : "ambiguous";
7221
+ if (alias.revision !== input.expectedRevision || alias.status !== expectedStatus) throw new NetworkPersonRegistryError("stale_review", "source alias changed; reload before reviewing");
7222
+ if (input.decision === "reopen") {
7223
+ const next2 = { ...alias, status: "ambiguous", revision: alias.revision + 1, updatedAt: now };
7224
+ await db.transact([
7225
+ { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...next2 } },
7226
+ networkPersonEventOp("alias_reopened", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7227
+ ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "excluded" } }] });
7228
+ return next2;
7229
+ }
7137
7230
  if (input.decision === "exclude") {
7138
7231
  const next2 = { ...alias, status: "excluded", revision: alias.revision + 1, updatedAt: now };
7139
7232
  await db.transact([
7140
7233
  { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...next2 } },
7141
- eventOp("alias_excluded", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7234
+ networkPersonEventOp("alias_excluded", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7142
7235
  ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } }] });
7143
7236
  return next2;
7144
7237
  }
@@ -7154,7 +7247,7 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
7154
7247
  { t: "retract", ns: "networkPersonAliases", id: alias.id, attrs: ["candidatePersonIds"] },
7155
7248
  ...missing.map((claim) => ({ t: "update", ns: "networkIdentityClaims", id: claim.id, attrs: { ...claim, personId: canonicalId, createdAt: now } })),
7156
7249
  ...await linkOps(canonicalId, alias.chapterLinks, now),
7157
- eventOp("alias_linked", mutationId, actor, now, { identityCount: alias.identityClaims.length }, { personId: canonicalId, aliasId: alias.id })
7250
+ networkPersonEventOp("alias_linked", mutationId, actor, now, { identityCount: alias.identityClaims.length }, { personId: canonicalId, aliasId: alias.id })
7158
7251
  ], { mutationId, guards: [
7159
7252
  { ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } },
7160
7253
  ...missing.map((claim) => ({ ns: "networkIdentityClaims", id: claim.id, exists: false }))
@@ -7184,7 +7277,7 @@ async function mergeNetworkPersons(db, input, mutationId, actor, now = Date.now(
7184
7277
  await db.transact([
7185
7278
  { t: "update", ns: "networkPersons", id: source.id, attrs: { ...sourceNext } },
7186
7279
  { t: "update", ns: "networkPersons", id: target.id, attrs: { ...targetNext } },
7187
- eventOp("persons_merged", mutationId, actor, now, { targetPersonId: target.id }, { personId: source.id })
7280
+ networkPersonEventOp("persons_merged", mutationId, actor, now, { targetPersonId: target.id }, { personId: source.id })
7188
7281
  ], { mutationId, guards: [
7189
7282
  { ns: "networkPersons", id: source.id, exists: true, equals: { revision: source.revision, state: "active" } },
7190
7283
  { ns: "networkPersons", id: target.id, exists: true, equals: { revision: target.revision, state: "active" } }
@@ -7200,7 +7293,7 @@ async function unmergeNetworkPerson(db, input, mutationId, actor, now = Date.now
7200
7293
  await db.transact([
7201
7294
  { t: "update", ns: "networkPersons", id: person.id, attrs: { state: "active", revision: next.revision, updatedAt: now } },
7202
7295
  { t: "retract", ns: "networkPersons", id: person.id, attrs: ["mergedIntoId"] },
7203
- eventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
7296
+ networkPersonEventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
7204
7297
  ], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "merged", mergedIntoId: person.mergedIntoId } }] });
7205
7298
  return next;
7206
7299
  }
@@ -7214,50 +7307,16 @@ async function suppressNetworkPerson(db, input, mutationId, actor, now = Date.no
7214
7307
  const next = { ...person, contactPolicy: policy, revision: person.revision + 1, updatedAt: now };
7215
7308
  await db.transact([
7216
7309
  { 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 })
7310
+ networkPersonEventOp("contact_suppression_changed", mutationId, actor, now, { marketingSuppressed: policy.marketingSuppressed, discretionarySuppressed: policy.discretionarySuppressed }, { personId: person.id })
7218
7311
  ], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision } }] });
7219
7312
  return next;
7220
7313
  }
7221
7314
 
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");
7227
- }
7228
- 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
7244
- }
7245
- };
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
- }
7252
-
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}$/;
7315
+ // src/network-person-route-projection.ts
7257
7316
  function redactedName(displayName) {
7258
7317
  return displayName.split(/\s+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}\u2026`).join(" ");
7259
7318
  }
7260
- var redactedLink = async (link) => ({
7319
+ var redactedNetworkPersonLink = async (link) => ({
7261
7320
  id: link.id,
7262
7321
  personId: link.personId,
7263
7322
  chapterId: link.chapterId,
@@ -7265,13 +7324,13 @@ var redactedLink = async (link) => ({
7265
7324
  state: link.state,
7266
7325
  createdAt: link.createdAt
7267
7326
  });
7268
- var safePolicy = (policy) => ({
7327
+ var safeNetworkContactPolicy = (policy) => ({
7269
7328
  marketingSuppressed: policy.marketingSuppressed,
7270
7329
  discretionarySuppressed: policy.discretionarySuppressed,
7271
7330
  hasReason: Boolean(policy.reason),
7272
7331
  updatedAt: policy.updatedAt
7273
7332
  });
7274
- var safeAlias = (alias) => ({
7333
+ var safeNetworkPersonAlias = async (alias) => ({
7275
7334
  id: alias.id,
7276
7335
  sourceSystem: alias.sourceSystem,
7277
7336
  displayNameHint: redactedName(alias.displayName),
@@ -7286,17 +7345,91 @@ var safeAlias = (alias) => ({
7286
7345
  },
7287
7346
  identityClaims: alias.identityClaims.map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7288
7347
  chapterIds: [...new Set(alias.chapterLinks.map((link) => link.chapterId))],
7348
+ chapterSources: await Promise.all(alias.chapterLinks.map(async (link) => ({
7349
+ chapterId: link.chapterId,
7350
+ sourceKey: await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId)
7351
+ }))),
7289
7352
  revision: alias.revision,
7290
7353
  updatedAt: alias.updatedAt
7291
7354
  });
7292
- var safePerson = (person) => ({
7355
+ var safeNetworkPerson = (person, claims = []) => ({
7293
7356
  id: person.id,
7357
+ displayName: person.displayName,
7294
7358
  displayNameHint: redactedName(person.displayName),
7359
+ 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
7360
  state: person.state,
7296
7361
  mergedIntoId: person.mergedIntoId ?? null,
7297
7362
  revision: person.revision,
7298
- contactPolicy: safePolicy(person.contactPolicy)
7363
+ contactPolicy: safeNetworkContactPolicy(person.contactPolicy)
7299
7364
  });
7365
+ async function projectNetworkPersonRegistryState(state) {
7366
+ const persons = await Promise.all(state.persons.map(async (person) => {
7367
+ const claims = state.claims.filter((claim) => claim.personId === person.id);
7368
+ const links = state.links.filter((link) => link.personId === person.id && link.state === "shadow");
7369
+ const safeLinks = await Promise.all(links.map(redactedNetworkPersonLink));
7370
+ const canonicalId = canonicalNetworkPersonId(person.id, state.persons);
7371
+ const canonical2 = state.persons.find((candidate) => candidate.id === canonicalId) ?? person;
7372
+ return {
7373
+ ...safeNetworkPerson(person, claims),
7374
+ canonicalPersonId: canonicalId,
7375
+ identityClaims: claims.map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7376
+ chapterLinks: safeLinks,
7377
+ chapterProjections: projectNetworkContactPolicy(canonical2.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safeNetworkContactPolicy(contactPolicy) }))
7378
+ };
7379
+ }));
7380
+ const aliases = await Promise.all(state.aliases.map(safeNetworkPersonAlias));
7381
+ return {
7382
+ persons,
7383
+ aliases,
7384
+ reviewQueue: aliases.filter((alias) => alias.status === "ambiguous"),
7385
+ excluded: aliases.filter((alias) => alias.status === "excluded"),
7386
+ recentEvents: state.events.slice(0, 100).map((event) => ({
7387
+ id: event.id,
7388
+ kind: event.kind,
7389
+ personId: event.personId ?? null,
7390
+ aliasId: event.aliasId ?? null,
7391
+ actorId: event.actorId,
7392
+ mutationId: event.mutationId,
7393
+ createdAt: event.createdAt
7394
+ }))
7395
+ };
7396
+ }
7397
+
7398
+ // src/network-person-reveal.ts
7399
+ async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, now = Date.now()) {
7400
+ const reason = input.reason.trim();
7401
+ if (!reason || reason.length > 500) {
7402
+ throw new NetworkPersonRegistryError("invalid_reveal", "a bounded reveal reason is required");
7403
+ }
7404
+ const state = await loadNetworkPersonRegistry(db);
7405
+ const target = input.kind === "person" ? state.persons.find((row) => row.id === input.id) : state.aliases.find((row) => row.id === input.id);
7406
+ if (!target) throw new NetworkPersonRegistryError("reveal_not_found", "registry record not found");
7407
+ const ids = input.kind === "person" ? { personId: input.id } : { aliasId: input.id };
7408
+ const event = {
7409
+ t: "update",
7410
+ ns: "networkPersonEvents",
7411
+ id: `network_person_event:${mutationId}`,
7412
+ attrs: {
7413
+ id: `network_person_event:${mutationId}`,
7414
+ kind: "pii_revealed",
7415
+ actorId: actor.id,
7416
+ ...ids,
7417
+ mutationId,
7418
+ detail: { targetKind: input.kind, reason },
7419
+ createdAt: now
7420
+ }
7421
+ };
7422
+ await db.transact([event], {
7423
+ mutationId,
7424
+ guards: [{ ns: "networkPersonEvents", id: `network_person_event:${mutationId}`, exists: false }]
7425
+ });
7426
+ return input.kind === "person" ? { kind: "person", person: target, identities: state.claims.filter((claim) => claim.personId === input.id) } : { kind: "alias", alias: target };
7427
+ }
7428
+
7429
+ // src/worker-routes-network-person-registry.ts
7430
+ var PATH2 = "/api/admin/network-person-registry";
7431
+ var MAX_BODY2 = 32e3;
7432
+ var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
7300
7433
  var positiveRevision = (value) => {
7301
7434
  const parsed = Number(value);
7302
7435
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
@@ -7352,14 +7485,14 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7352
7485
  return json({
7353
7486
  result: {
7354
7487
  status: result.status,
7355
- alias: safeAlias(result.alias),
7356
- ..."person" in result && result.person ? { person: safePerson(result.person) } : {},
7488
+ alias: await safeNetworkPersonAlias(result.alias),
7489
+ ..."person" in result && result.person ? { person: safeNetworkPerson(result.person, result.alias.identityClaims) } : {},
7357
7490
  ..."replayed" in result ? { replayed: result.replayed } : {}
7358
7491
  }
7359
7492
  }, 201);
7360
7493
  }
7361
7494
  if (command === "review") {
7362
- if (input.decision !== "link" && input.decision !== "exclude") return json({ error: "review decision must be link or exclude" }, 400);
7495
+ if (input.decision !== "link" && input.decision !== "exclude" && input.decision !== "reopen") return json({ error: "review decision must be link, exclude, or reopen" }, 400);
7363
7496
  const expectedRevision = positiveRevision(input.expectedRevision);
7364
7497
  if (!expectedRevision) return json({ error: "expectedRevision is required" }, 400);
7365
7498
  if (input.decision === "link" && typeof input.personId !== "string") return json({ error: "personId is required when linking" }, 400);
@@ -7369,7 +7502,42 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7369
7502
  decision: input.decision,
7370
7503
  ...typeof input.personId === "string" ? { personId: input.personId } : {}
7371
7504
  }, mutationId, actionActor);
7372
- return json({ result: safeAlias(result) });
7505
+ return json({ result: await safeNetworkPersonAlias(result) });
7506
+ }
7507
+ if (command === "link-source") {
7508
+ const expectedPersonRevision = positiveRevision(input.expectedPersonRevision);
7509
+ const chapterId = typeof input.chapterId === "string" ? input.chapterId : "";
7510
+ const chapterPersonId = typeof input.chapterPersonId === "string" ? input.chapterPersonId.trim() : "";
7511
+ const verifiedEmail = typeof input.verifiedEmail === "string" ? input.verifiedEmail.trim() : "";
7512
+ 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);
7513
+ const result = await linkNetworkChapterPerson(db, {
7514
+ personId: typeof input.personId === "string" ? input.personId : "",
7515
+ expectedPersonRevision,
7516
+ chapterId,
7517
+ chapterPersonId,
7518
+ verifiedEmail
7519
+ }, mutationId, actionActor);
7520
+ return json({
7521
+ result: {
7522
+ person: safeNetworkPerson(result.person),
7523
+ link: await redactedNetworkPersonLink(result.link),
7524
+ ...result.replayed ? { replayed: true } : {}
7525
+ }
7526
+ });
7527
+ }
7528
+ if (command === "unlink-source") {
7529
+ const expectedPersonRevision = positiveRevision(input.expectedPersonRevision);
7530
+ const reason = typeof input.reason === "string" ? input.reason.trim() : "";
7531
+ if (!expectedPersonRevision || !reason || reason.length > 500) {
7532
+ return json({ error: "a Person revision and bounded unlink reason are required" }, 400);
7533
+ }
7534
+ const result = await unlinkNetworkChapterPerson(db, {
7535
+ personId: typeof input.personId === "string" ? input.personId : "",
7536
+ linkId: typeof input.linkId === "string" ? input.linkId : "",
7537
+ expectedPersonRevision,
7538
+ reason
7539
+ }, mutationId, actionActor);
7540
+ return json({ result: { person: safeNetworkPerson(result.person), linkState: result.link.state } });
7373
7541
  }
7374
7542
  if (command === "merge") {
7375
7543
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7381,7 +7549,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7381
7549
  expectedRevision,
7382
7550
  expectedTargetRevision
7383
7551
  }, mutationId, actionActor);
7384
- return json({ result: { source: safePerson(result.source), target: safePerson(result.target) } });
7552
+ return json({ result: { source: safeNetworkPerson(result.source), target: safeNetworkPerson(result.target) } });
7385
7553
  }
7386
7554
  if (command === "unmerge") {
7387
7555
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7390,7 +7558,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7390
7558
  personId: typeof input.personId === "string" ? input.personId : "",
7391
7559
  expectedRevision
7392
7560
  }, mutationId, actionActor);
7393
- return json({ result: safePerson(result) });
7561
+ return json({ result: safeNetworkPerson(result) });
7394
7562
  }
7395
7563
  if (command === "suppression") {
7396
7564
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7406,7 +7574,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7406
7574
  discretionarySuppressed: input.discretionarySuppressed,
7407
7575
  ...reason ? { reason } : {}
7408
7576
  }, mutationId, actionActor);
7409
- return json({ result: safePerson(result) });
7577
+ return json({ result: safeNetworkPerson(result) });
7410
7578
  }
7411
7579
  if (command === "reveal") {
7412
7580
  if (input.kind !== "person" && input.kind !== "alias") return json({ error: "reveal kind must be person or alias" }, 400);
@@ -7425,48 +7593,19 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7425
7593
  }
7426
7594
  }
7427
7595
  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);
7596
+ const projection = await projectNetworkPersonRegistryState(state);
7447
7597
  return json({
7448
7598
  version: 1,
7449
7599
  mode: "shadow",
7450
7600
  writable: true,
7451
7601
  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,
7602
+ // The store pages every collection to its explicit 10,000-row operational
7603
+ // bound and rejects larger state instead of returning a partial projection.
7604
+ chapterLinkProjectionComplete: true,
7605
+ personProjectionComplete: true,
7606
+ aliasProjectionComplete: true,
7456
7607
  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
- }))
7608
+ ...projection
7470
7609
  });
7471
7610
  };
7472
7611
 
@@ -7659,12 +7798,12 @@ function projectNetworkCommercialEvents(input, sources) {
7659
7798
  const prior = latest.get(key);
7660
7799
  if (!prior || order(prior, fact) <= 0) latest.set(key, row);
7661
7800
  }
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")) {
7801
+ const rows5 = [...latest.values()];
7802
+ const customers = rows5.filter((row) => row.objectKind === "customer");
7803
+ const subscriptions = rows5.filter((row) => row.objectKind === "subscription");
7804
+ const invoices = rows5.filter((row) => row.objectKind === "invoice");
7805
+ const refunds = rows5.filter((row) => row.objectKind === "refund" && row.eventType.startsWith("refund."));
7806
+ for (const charge of rows5.filter((row) => row.eventType === "charge.refunded")) {
7668
7807
  if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
7669
7808
  exceptions.push({ eventId: charge.eventId, kind: "charge_refund_without_refund_object", sourceKey: charge.sourceKey, detail: { eventType: charge.eventType, objectId: charge.objectId } });
7670
7809
  }
@@ -7693,7 +7832,7 @@ var materializedNamespaces = [
7693
7832
  "networkCommercialInvoices",
7694
7833
  "networkCommercialRefunds"
7695
7834
  ];
7696
- async function rows5(db, ns) {
7835
+ async function rows4(db, ns) {
7697
7836
  const all = [];
7698
7837
  const pageSize = 500;
7699
7838
  for (let offset = 0; ; offset += pageSize) {
@@ -7704,9 +7843,9 @@ async function rows5(db, ns) {
7704
7843
  }
7705
7844
  }
7706
7845
  async function loadNetworkCommercialProjection(db, sources) {
7707
- const stored = await rows5(db, "networkCommercialEvents");
7846
+ const stored = await rows4(db, "networkCommercialEvents");
7708
7847
  const projection = projectNetworkCommercialEvents(stored.map((row) => row.fact), sources);
7709
- const persisted = await rows5(db, "networkCommercialExceptions");
7848
+ const persisted = await rows4(db, "networkCommercialExceptions");
7710
7849
  for (const row of persisted) {
7711
7850
  if (row.status !== "active" || row.kind !== "collision") continue;
7712
7851
  if (projection.exceptions.some((item) => item.eventId === row.eventId && item.kind === "collision")) continue;
@@ -7744,8 +7883,8 @@ function projectionOps(projection, now) {
7744
7883
  async function rebuildNetworkCommercialProjection(db, sources, mutationId, now = Date.now()) {
7745
7884
  const projection = await loadNetworkCommercialProjection(db, sources);
7746
7885
  const [existing, ...materialized] = await Promise.all([
7747
- rows5(db, "networkCommercialExceptions"),
7748
- ...materializedNamespaces.map((ns) => rows5(db, ns))
7886
+ rows4(db, "networkCommercialExceptions"),
7887
+ ...materializedNamespaces.map((ns) => rows4(db, ns))
7749
7888
  ]);
7750
7889
  const activeIds = new Set(projection.exceptions.map((item) => exceptionId(item.eventId, item.kind)));
7751
7890
  const projectedIds = new Set(projectionOps(projection, now).map((op) => String(op.id)));
@@ -7854,11 +7993,11 @@ async function listStripeObjects(client, path, params = {}, maxPages = 20) {
7854
7993
  if (!Array.isArray(page.data) || typeof page.has_more !== "boolean") {
7855
7994
  throw new Error("Stripe returned an invalid commercial list response");
7856
7995
  }
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);
7996
+ const rows5 = page.data.map(asRaw).filter((row) => row !== null);
7997
+ if (rows5.length !== page.data.length) throw new Error("Stripe returned a malformed commercial object");
7998
+ objects.push(...rows5);
7860
7999
  if (!page.has_more) return { objects, complete: true };
7861
- const cursor = rows6.at(-1)?.id;
8000
+ const cursor = rows5.at(-1)?.id;
7862
8001
  if (typeof cursor !== "string" || !cursor) throw new Error("Stripe pagination did not provide a cursor");
7863
8002
  startingAfter = cursor;
7864
8003
  }
@@ -8110,14 +8249,14 @@ var PAGE2 = 500;
8110
8249
  var MAX_SCAN = 5e3;
8111
8250
  var MAX_RECORDS2 = 500;
8112
8251
  async function localCommercialRows(db) {
8113
- const rows6 = [];
8252
+ const rows5 = [];
8114
8253
  for (let offset = 0; offset < MAX_SCAN; offset += PAGE2) {
8115
8254
  const result = await db.query({ applications: { $: { limit: PAGE2, offset, order: { createdAt: "asc" } } } });
8116
8255
  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 };
8256
+ rows5.push(...page.slice(0, MAX_SCAN - rows5.length));
8257
+ if (page.length < PAGE2) return { rows: rows5, truncated: false };
8119
8258
  }
8120
- return { rows: rows6, truncated: true };
8259
+ return { rows: rows5, truncated: true };
8121
8260
  }
8122
8261
  var handleNetworkCommercialParity = async (req, url, env, ctx) => {
8123
8262
  if (req.method !== "GET" || url.pathname !== PARITY_PATH) return null;
@@ -8676,17 +8815,17 @@ async function builderLibraryDigest(document) {
8676
8815
  var control2 = (row) => row?.document && typeof row.document === "object" ? row : null;
8677
8816
  var local2 = (row) => row?.document && typeof row.document === "object" ? row : null;
8678
8817
  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) : [];
8818
+ const rows5 = (await db.query({ builderLibraryControls: { $: { where: { targetId, environment, active: true }, limit: 500 } } })).builderLibraryControls;
8819
+ return Array.isArray(rows5) ? rows5.map(control2).filter(Boolean) : [];
8681
8820
  }
8682
8821
  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);
8822
+ const rows5 = (await db.query({ builderLibraryControls: { $: { where: { sourceId, targetId, environment }, order: { revision: "desc" }, limit: 20 } } })).builderLibraryControls;
8823
+ return control2(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
8685
8824
  }
8686
8825
  async function loadLocalBuilderLibrary(db, appId, environment) {
8687
8826
  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);
8827
+ const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id }, limit: 1 } } })).builderLibraryLocal;
8828
+ return local2(Array.isArray(rows5) ? rows5[0] : void 0);
8690
8829
  }
8691
8830
  async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
8692
8831
  const { document, digest: digest2 } = envelope;