@odla-ai/chapter 0.47.3 → 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;
@@ -6993,6 +6993,9 @@ async function networkSourceKey(sourceSystem, sourceRecordId) {
6993
6993
  async function networkChapterLinkId(personId, chapterId, chapterPersonId) {
6994
6994
  return `chapter_person_${await sha256(`${personId}\0${chapterId}\0${chapterPersonId}`)}`;
6995
6995
  }
6996
+ async function networkChapterPersonSourceKey(chapterId, chapterPersonId) {
6997
+ return `chapter_source_${await sha256(`${chapterId}\0${chapterPersonId}`)}`;
6998
+ }
6996
6999
  function projectNetworkContactPolicy(policy, links) {
6997
7000
  return links.map((link) => ({ ...link, contactPolicy: { ...policy } }));
6998
7001
  }
@@ -7010,7 +7013,7 @@ function canonicalNetworkPersonId(personId, persons) {
7010
7013
  }
7011
7014
  }
7012
7015
 
7013
- // src/network-person-store.ts
7016
+ // src/network-person-store-state.ts
7014
7017
  var NetworkPersonRegistryError = class extends Error {
7015
7018
  constructor(code, message) {
7016
7019
  super(message);
@@ -7018,29 +7021,112 @@ var NetworkPersonRegistryError = class extends Error {
7018
7021
  }
7019
7022
  code;
7020
7023
  };
7021
- var rows4 = (value) => Array.isArray(value) ? value : [];
7022
- async function loadNetworkPersonRegistry(db) {
7023
- const result = await db.query({
7024
- networkPersons: { $: { order: { updatedAt: "desc" }, limit: 1e3 } },
7025
- networkIdentityClaims: { $: { limit: 1e3 } },
7026
- networkPersonAliases: { $: { order: { updatedAt: "desc" }, limit: 1e3 } },
7027
- networkChapterPersonLinks: { $: { limit: 1e3 } },
7028
- networkPersonEvents: { $: { order: { createdAt: "desc" }, limit: 1e3 } }
7029
- });
7030
- return {
7031
- persons: rows4(result.networkPersons),
7032
- claims: rows4(result.networkIdentityClaims),
7033
- aliases: rows4(result.networkPersonAliases),
7034
- links: rows4(result.networkChapterPersonLinks),
7035
- events: rows4(result.networkPersonEvents)
7036
- };
7037
- }
7038
- var eventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
7024
+ var networkPersonEventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
7039
7025
  t: "update",
7040
7026
  ns: "networkPersonEvents",
7041
7027
  id: `network_person_event:${mutationId}`,
7042
7028
  attrs: { id: `network_person_event:${mutationId}`, kind, actorId: actor.id, ...ids, mutationId, detail, createdAt: now }
7043
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
7044
7130
  var defaultPolicy = (now, consent) => ({
7045
7131
  marketingSuppressed: consent.marketing !== "granted",
7046
7132
  discretionarySuppressed: consent.discretionary !== "granted",
@@ -7050,7 +7136,8 @@ var isGuardConflict = (error) => error instanceof Error && error.code === "trans
7050
7136
  async function linkOps(personId, links, now) {
7051
7137
  return Promise.all(links.map(async (link) => {
7052
7138
  const id = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
7053
- 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 } };
7054
7141
  }));
7055
7142
  }
7056
7143
  async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
@@ -7072,7 +7159,7 @@ async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId
7072
7159
  };
7073
7160
  await db.transact([
7074
7161
  { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...alias } },
7075
- 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 })
7076
7163
  ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: false }] });
7077
7164
  return { status: "ambiguous", alias };
7078
7165
  }
@@ -7108,7 +7195,7 @@ async function importNetworkPerson(db, input, mutationId, actor, now = Date.now(
7108
7195
  { t: "update", ns: "networkPersonAliases", id: aliasId, attrs: { ...alias } },
7109
7196
  ...claimOps,
7110
7197
  ...await linkOps(personId, input.chapterLinks ?? [], now),
7111
- 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 })
7112
7199
  ];
7113
7200
  try {
7114
7201
  await db.transact(ops, { mutationId, guards: [
@@ -7130,12 +7217,21 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
7130
7217
  const state = await loadNetworkPersonRegistry(db);
7131
7218
  const alias = state.aliases.find((row) => row.id === input.aliasId);
7132
7219
  if (!alias) throw new NetworkPersonRegistryError("alias_not_found", "source alias not found");
7133
- 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
+ }
7134
7230
  if (input.decision === "exclude") {
7135
7231
  const next2 = { ...alias, status: "excluded", revision: alias.revision + 1, updatedAt: now };
7136
7232
  await db.transact([
7137
7233
  { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...next2 } },
7138
- 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 })
7139
7235
  ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } }] });
7140
7236
  return next2;
7141
7237
  }
@@ -7151,7 +7247,7 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
7151
7247
  { t: "retract", ns: "networkPersonAliases", id: alias.id, attrs: ["candidatePersonIds"] },
7152
7248
  ...missing.map((claim) => ({ t: "update", ns: "networkIdentityClaims", id: claim.id, attrs: { ...claim, personId: canonicalId, createdAt: now } })),
7153
7249
  ...await linkOps(canonicalId, alias.chapterLinks, now),
7154
- 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 })
7155
7251
  ], { mutationId, guards: [
7156
7252
  { ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } },
7157
7253
  ...missing.map((claim) => ({ ns: "networkIdentityClaims", id: claim.id, exists: false }))
@@ -7181,7 +7277,7 @@ async function mergeNetworkPersons(db, input, mutationId, actor, now = Date.now(
7181
7277
  await db.transact([
7182
7278
  { t: "update", ns: "networkPersons", id: source.id, attrs: { ...sourceNext } },
7183
7279
  { t: "update", ns: "networkPersons", id: target.id, attrs: { ...targetNext } },
7184
- 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 })
7185
7281
  ], { mutationId, guards: [
7186
7282
  { ns: "networkPersons", id: source.id, exists: true, equals: { revision: source.revision, state: "active" } },
7187
7283
  { ns: "networkPersons", id: target.id, exists: true, equals: { revision: target.revision, state: "active" } }
@@ -7197,7 +7293,7 @@ async function unmergeNetworkPerson(db, input, mutationId, actor, now = Date.now
7197
7293
  await db.transact([
7198
7294
  { t: "update", ns: "networkPersons", id: person.id, attrs: { state: "active", revision: next.revision, updatedAt: now } },
7199
7295
  { t: "retract", ns: "networkPersons", id: person.id, attrs: ["mergedIntoId"] },
7200
- eventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
7296
+ networkPersonEventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
7201
7297
  ], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "merged", mergedIntoId: person.mergedIntoId } }] });
7202
7298
  return next;
7203
7299
  }
@@ -7211,63 +7307,30 @@ async function suppressNetworkPerson(db, input, mutationId, actor, now = Date.no
7211
7307
  const next = { ...person, contactPolicy: policy, revision: person.revision + 1, updatedAt: now };
7212
7308
  await db.transact([
7213
7309
  { t: "update", ns: "networkPersons", id: person.id, attrs: { ...next } },
7214
- 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 })
7215
7311
  ], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision } }] });
7216
7312
  return next;
7217
7313
  }
7218
7314
 
7219
- // src/network-person-reveal.ts
7220
- async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, now = Date.now()) {
7221
- const reason = input.reason.trim();
7222
- if (!reason || reason.length > 500) {
7223
- throw new NetworkPersonRegistryError("invalid_reveal", "a bounded reveal reason is required");
7224
- }
7225
- const state = await loadNetworkPersonRegistry(db);
7226
- const target = input.kind === "person" ? state.persons.find((row) => row.id === input.id) : state.aliases.find((row) => row.id === input.id);
7227
- if (!target) throw new NetworkPersonRegistryError("reveal_not_found", "registry record not found");
7228
- const ids = input.kind === "person" ? { personId: input.id } : { aliasId: input.id };
7229
- const event = {
7230
- t: "update",
7231
- ns: "networkPersonEvents",
7232
- id: `network_person_event:${mutationId}`,
7233
- attrs: {
7234
- id: `network_person_event:${mutationId}`,
7235
- kind: "pii_revealed",
7236
- actorId: actor.id,
7237
- ...ids,
7238
- mutationId,
7239
- detail: { targetKind: input.kind, reason },
7240
- createdAt: now
7241
- }
7242
- };
7243
- await db.transact([event], {
7244
- mutationId,
7245
- guards: [{ ns: "networkPersonEvents", id: `network_person_event:${mutationId}`, exists: false }]
7246
- });
7247
- return input.kind === "person" ? { kind: "person", person: target, identities: state.claims.filter((claim) => claim.personId === input.id) } : { kind: "alias", alias: target };
7248
- }
7249
-
7250
- // src/worker-routes-network-person-registry.ts
7251
- var PATH2 = "/api/admin/network-person-registry";
7252
- var MAX_BODY2 = 32e3;
7253
- var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
7315
+ // src/network-person-route-projection.ts
7254
7316
  function redactedName(displayName) {
7255
7317
  return displayName.split(/\s+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}\u2026`).join(" ");
7256
7318
  }
7257
- var redactedLink = (link) => ({
7319
+ var redactedNetworkPersonLink = async (link) => ({
7258
7320
  id: link.id,
7259
7321
  personId: link.personId,
7260
7322
  chapterId: link.chapterId,
7323
+ sourceKey: await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId),
7261
7324
  state: link.state,
7262
7325
  createdAt: link.createdAt
7263
7326
  });
7264
- var safePolicy = (policy) => ({
7327
+ var safeNetworkContactPolicy = (policy) => ({
7265
7328
  marketingSuppressed: policy.marketingSuppressed,
7266
7329
  discretionarySuppressed: policy.discretionarySuppressed,
7267
7330
  hasReason: Boolean(policy.reason),
7268
7331
  updatedAt: policy.updatedAt
7269
7332
  });
7270
- var safeAlias = (alias) => ({
7333
+ var safeNetworkPersonAlias = async (alias) => ({
7271
7334
  id: alias.id,
7272
7335
  sourceSystem: alias.sourceSystem,
7273
7336
  displayNameHint: redactedName(alias.displayName),
@@ -7282,17 +7345,91 @@ var safeAlias = (alias) => ({
7282
7345
  },
7283
7346
  identityClaims: alias.identityClaims.map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7284
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
+ }))),
7285
7352
  revision: alias.revision,
7286
7353
  updatedAt: alias.updatedAt
7287
7354
  });
7288
- var safePerson = (person) => ({
7355
+ var safeNetworkPerson = (person, claims = []) => ({
7289
7356
  id: person.id,
7357
+ displayName: person.displayName,
7290
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)),
7291
7360
  state: person.state,
7292
7361
  mergedIntoId: person.mergedIntoId ?? null,
7293
7362
  revision: person.revision,
7294
- contactPolicy: safePolicy(person.contactPolicy)
7363
+ contactPolicy: safeNetworkContactPolicy(person.contactPolicy)
7295
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}$/;
7296
7433
  var positiveRevision = (value) => {
7297
7434
  const parsed = Number(value);
7298
7435
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
@@ -7348,14 +7485,14 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7348
7485
  return json({
7349
7486
  result: {
7350
7487
  status: result.status,
7351
- alias: safeAlias(result.alias),
7352
- ..."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) } : {},
7353
7490
  ..."replayed" in result ? { replayed: result.replayed } : {}
7354
7491
  }
7355
7492
  }, 201);
7356
7493
  }
7357
7494
  if (command === "review") {
7358
- 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);
7359
7496
  const expectedRevision = positiveRevision(input.expectedRevision);
7360
7497
  if (!expectedRevision) return json({ error: "expectedRevision is required" }, 400);
7361
7498
  if (input.decision === "link" && typeof input.personId !== "string") return json({ error: "personId is required when linking" }, 400);
@@ -7365,7 +7502,42 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7365
7502
  decision: input.decision,
7366
7503
  ...typeof input.personId === "string" ? { personId: input.personId } : {}
7367
7504
  }, mutationId, actionActor);
7368
- 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 } });
7369
7541
  }
7370
7542
  if (command === "merge") {
7371
7543
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7377,7 +7549,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7377
7549
  expectedRevision,
7378
7550
  expectedTargetRevision
7379
7551
  }, mutationId, actionActor);
7380
- return json({ result: { source: safePerson(result.source), target: safePerson(result.target) } });
7552
+ return json({ result: { source: safeNetworkPerson(result.source), target: safeNetworkPerson(result.target) } });
7381
7553
  }
7382
7554
  if (command === "unmerge") {
7383
7555
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7386,7 +7558,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7386
7558
  personId: typeof input.personId === "string" ? input.personId : "",
7387
7559
  expectedRevision
7388
7560
  }, mutationId, actionActor);
7389
- return json({ result: safePerson(result) });
7561
+ return json({ result: safeNetworkPerson(result) });
7390
7562
  }
7391
7563
  if (command === "suppression") {
7392
7564
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7402,7 +7574,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7402
7574
  discretionarySuppressed: input.discretionarySuppressed,
7403
7575
  ...reason ? { reason } : {}
7404
7576
  }, mutationId, actionActor);
7405
- return json({ result: safePerson(result) });
7577
+ return json({ result: safeNetworkPerson(result) });
7406
7578
  }
7407
7579
  if (command === "reveal") {
7408
7580
  if (input.kind !== "person" && input.kind !== "alias") return json({ error: "reveal kind must be person or alias" }, 400);
@@ -7421,43 +7593,19 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7421
7593
  }
7422
7594
  }
7423
7595
  const state = await loadNetworkPersonRegistry(db);
7424
- const persons = state.persons.map((person) => {
7425
- const links = state.links.filter((link) => link.personId === person.id);
7426
- const canonicalId = canonicalNetworkPersonId(person.id, state.persons);
7427
- const canonical2 = state.persons.find((candidate) => candidate.id === canonicalId) ?? person;
7428
- return {
7429
- id: person.id,
7430
- displayNameHint: redactedName(person.displayName),
7431
- state: person.state,
7432
- canonicalPersonId: canonicalId,
7433
- mergedIntoId: person.mergedIntoId ?? null,
7434
- revision: person.revision,
7435
- contactPolicy: safePolicy(person.contactPolicy),
7436
- identityClaims: state.claims.filter((claim) => claim.personId === person.id).map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7437
- chapterLinks: links.map(redactedLink),
7438
- chapterProjections: projectNetworkContactPolicy(canonical2.contactPolicy, links.map(redactedLink)).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safePolicy(contactPolicy) }))
7439
- };
7440
- });
7441
- const aliases = state.aliases.map(safeAlias);
7596
+ const projection = await projectNetworkPersonRegistryState(state);
7442
7597
  return json({
7443
7598
  version: 1,
7444
7599
  mode: "shadow",
7445
7600
  writable: true,
7446
7601
  authoritativeForChapterOperation: false,
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,
7447
7607
  configuredChapterIds: ctx.chapter.networkPersonRegistry.chapterIds,
7448
- persons,
7449
- aliases,
7450
- reviewQueue: aliases.filter((alias) => alias.status === "ambiguous"),
7451
- excluded: aliases.filter((alias) => alias.status === "excluded"),
7452
- recentEvents: state.events.slice(0, 100).map((event) => ({
7453
- id: event.id,
7454
- kind: event.kind,
7455
- personId: event.personId ?? null,
7456
- aliasId: event.aliasId ?? null,
7457
- actorId: event.actorId,
7458
- mutationId: event.mutationId,
7459
- createdAt: event.createdAt
7460
- }))
7608
+ ...projection
7461
7609
  });
7462
7610
  };
7463
7611
 
@@ -7650,12 +7798,12 @@ function projectNetworkCommercialEvents(input, sources) {
7650
7798
  const prior = latest.get(key);
7651
7799
  if (!prior || order(prior, fact) <= 0) latest.set(key, row);
7652
7800
  }
7653
- const rows6 = [...latest.values()];
7654
- const customers = rows6.filter((row) => row.objectKind === "customer");
7655
- const subscriptions = rows6.filter((row) => row.objectKind === "subscription");
7656
- const invoices = rows6.filter((row) => row.objectKind === "invoice");
7657
- const refunds = rows6.filter((row) => row.objectKind === "refund" && row.eventType.startsWith("refund."));
7658
- 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")) {
7659
7807
  if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
7660
7808
  exceptions.push({ eventId: charge.eventId, kind: "charge_refund_without_refund_object", sourceKey: charge.sourceKey, detail: { eventType: charge.eventType, objectId: charge.objectId } });
7661
7809
  }
@@ -7684,7 +7832,7 @@ var materializedNamespaces = [
7684
7832
  "networkCommercialInvoices",
7685
7833
  "networkCommercialRefunds"
7686
7834
  ];
7687
- async function rows5(db, ns) {
7835
+ async function rows4(db, ns) {
7688
7836
  const all = [];
7689
7837
  const pageSize = 500;
7690
7838
  for (let offset = 0; ; offset += pageSize) {
@@ -7695,9 +7843,9 @@ async function rows5(db, ns) {
7695
7843
  }
7696
7844
  }
7697
7845
  async function loadNetworkCommercialProjection(db, sources) {
7698
- const stored = await rows5(db, "networkCommercialEvents");
7846
+ const stored = await rows4(db, "networkCommercialEvents");
7699
7847
  const projection = projectNetworkCommercialEvents(stored.map((row) => row.fact), sources);
7700
- const persisted = await rows5(db, "networkCommercialExceptions");
7848
+ const persisted = await rows4(db, "networkCommercialExceptions");
7701
7849
  for (const row of persisted) {
7702
7850
  if (row.status !== "active" || row.kind !== "collision") continue;
7703
7851
  if (projection.exceptions.some((item) => item.eventId === row.eventId && item.kind === "collision")) continue;
@@ -7735,8 +7883,8 @@ function projectionOps(projection, now) {
7735
7883
  async function rebuildNetworkCommercialProjection(db, sources, mutationId, now = Date.now()) {
7736
7884
  const projection = await loadNetworkCommercialProjection(db, sources);
7737
7885
  const [existing, ...materialized] = await Promise.all([
7738
- rows5(db, "networkCommercialExceptions"),
7739
- ...materializedNamespaces.map((ns) => rows5(db, ns))
7886
+ rows4(db, "networkCommercialExceptions"),
7887
+ ...materializedNamespaces.map((ns) => rows4(db, ns))
7740
7888
  ]);
7741
7889
  const activeIds = new Set(projection.exceptions.map((item) => exceptionId(item.eventId, item.kind)));
7742
7890
  const projectedIds = new Set(projectionOps(projection, now).map((op) => String(op.id)));
@@ -7845,11 +7993,11 @@ async function listStripeObjects(client, path, params = {}, maxPages = 20) {
7845
7993
  if (!Array.isArray(page.data) || typeof page.has_more !== "boolean") {
7846
7994
  throw new Error("Stripe returned an invalid commercial list response");
7847
7995
  }
7848
- const rows6 = page.data.map(asRaw).filter((row) => row !== null);
7849
- if (rows6.length !== page.data.length) throw new Error("Stripe returned a malformed commercial object");
7850
- 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);
7851
7999
  if (!page.has_more) return { objects, complete: true };
7852
- const cursor = rows6.at(-1)?.id;
8000
+ const cursor = rows5.at(-1)?.id;
7853
8001
  if (typeof cursor !== "string" || !cursor) throw new Error("Stripe pagination did not provide a cursor");
7854
8002
  startingAfter = cursor;
7855
8003
  }
@@ -8101,14 +8249,14 @@ var PAGE2 = 500;
8101
8249
  var MAX_SCAN = 5e3;
8102
8250
  var MAX_RECORDS2 = 500;
8103
8251
  async function localCommercialRows(db) {
8104
- const rows6 = [];
8252
+ const rows5 = [];
8105
8253
  for (let offset = 0; offset < MAX_SCAN; offset += PAGE2) {
8106
8254
  const result = await db.query({ applications: { $: { limit: PAGE2, offset, order: { createdAt: "asc" } } } });
8107
8255
  const page = Array.isArray(result.applications) ? result.applications : [];
8108
- rows6.push(...page.slice(0, MAX_SCAN - rows6.length));
8109
- 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 };
8110
8258
  }
8111
- return { rows: rows6, truncated: true };
8259
+ return { rows: rows5, truncated: true };
8112
8260
  }
8113
8261
  var handleNetworkCommercialParity = async (req, url, env, ctx) => {
8114
8262
  if (req.method !== "GET" || url.pathname !== PARITY_PATH) return null;
@@ -8667,17 +8815,17 @@ async function builderLibraryDigest(document) {
8667
8815
  var control2 = (row) => row?.document && typeof row.document === "object" ? row : null;
8668
8816
  var local2 = (row) => row?.document && typeof row.document === "object" ? row : null;
8669
8817
  async function loadActiveBuilderLibraryControls(db, targetId, environment) {
8670
- const rows6 = (await db.query({ builderLibraryControls: { $: { where: { targetId, environment, active: true }, limit: 500 } } })).builderLibraryControls;
8671
- 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) : [];
8672
8820
  }
8673
8821
  async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environment) {
8674
- const rows6 = (await db.query({ builderLibraryControls: { $: { where: { sourceId, targetId, environment }, order: { revision: "desc" }, limit: 20 } } })).builderLibraryControls;
8675
- 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);
8676
8824
  }
8677
8825
  async function loadLocalBuilderLibrary(db, appId, environment) {
8678
8826
  const id = `builder-library-local:${environment}`;
8679
- const rows6 = (await db.query({ builderLibraryLocal: { $: { where: { id }, limit: 1 } } })).builderLibraryLocal;
8680
- 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);
8681
8829
  }
8682
8830
  async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
8683
8831
  const { document, digest: digest2 } = envelope;