@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.
@@ -375,8 +375,8 @@ function tierPayable(tier, group, hasSecretKey) {
375
375
  function byOrder(a, b) {
376
376
  return a.sortOrder - b.sortOrder || a.id.localeCompare(b.id);
377
377
  }
378
- function resolveTiers(group, rows6 = []) {
379
- const stored = rows6.filter((tier) => tier.active);
378
+ function resolveTiers(group, rows5 = []) {
379
+ const stored = rows5.filter((tier) => tier.active);
380
380
  if (stored.length) return [...stored].sort(byOrder);
381
381
  return [{
382
382
  id: "standard",
@@ -422,15 +422,15 @@ function rowToTier(row, groupId) {
422
422
  }
423
423
  async function loadTiers(db, group) {
424
424
  const groupId = String(group.id ?? "");
425
- let rows6;
425
+ let rows5;
426
426
  try {
427
- rows6 = (await db.query({
427
+ rows5 = (await db.query({
428
428
  tiers: { $: { where: { groupId }, order: { sortOrder: "asc" }, limit: 100 } }
429
429
  })).tiers;
430
430
  } catch {
431
- rows6 = void 0;
431
+ rows5 = void 0;
432
432
  }
433
- const list = Array.isArray(rows6) ? rows6.map((row) => rowToTier(row, groupId)) : [];
433
+ const list = Array.isArray(rows5) ? rows5.map((row) => rowToTier(row, groupId)) : [];
434
434
  return resolveTiers(group, list);
435
435
  }
436
436
  async function loadOfferableTiers(db, group, hasSecretKey) {
@@ -1484,27 +1484,27 @@ async function hasSignupControl(db, targetId, environment, active) {
1484
1484
  if (db.aggregate) {
1485
1485
  return Number((await db.aggregate("signupControls", { count: true }, { where })).count ?? 0) > 0;
1486
1486
  }
1487
- const rows6 = (await db.query({ signupControls: {} })).signupControls;
1488
- return Array.isArray(rows6) && rows6.some((row) => row.targetId === targetId && row.environment === environment && (!active || row.active === true));
1487
+ const rows5 = (await db.query({ signupControls: {} })).signupControls;
1488
+ return Array.isArray(rows5) && rows5.some((row) => row.targetId === targetId && row.environment === environment && (!active || row.active === true));
1489
1489
  }
1490
1490
  async function loadLatestSignupControl(db, targetId, environment) {
1491
1491
  if (!await hasSignupControl(db, targetId, environment)) return null;
1492
- const rows6 = (await db.query({ signupControls: { $: {
1492
+ const rows5 = (await db.query({ signupControls: { $: {
1493
1493
  where: { targetId, environment },
1494
1494
  order: { revision: "desc" },
1495
1495
  limit: 1
1496
1496
  } } })).signupControls;
1497
- const latest = Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0] : void 0;
1497
+ const latest = Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0] : void 0;
1498
1498
  return asStored(latest);
1499
1499
  }
1500
1500
  async function loadActiveSignupControl(db, targetId, environment) {
1501
1501
  if (!await hasSignupControl(db, targetId, environment, true)) return null;
1502
- const rows6 = (await db.query({ signupControls: { $: {
1502
+ const rows5 = (await db.query({ signupControls: { $: {
1503
1503
  where: { targetId, environment, active: true },
1504
1504
  order: { revision: "desc" },
1505
1505
  limit: 1
1506
1506
  } } })).signupControls;
1507
- const active = Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0] : void 0;
1507
+ const active = Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0] : void 0;
1508
1508
  return asStored(active);
1509
1509
  }
1510
1510
  function controlledApplication(base, document) {
@@ -2880,33 +2880,33 @@ var control = (row) => row && row.document && typeof row.document === "object" ?
2880
2880
  var local = (row) => row && row.document && typeof row.document === "object" ? row : null;
2881
2881
  async function exists(db, ns, where) {
2882
2882
  if (db.aggregate) return Number((await db.aggregate(ns, { count: true }, { where })).count ?? 0) > 0;
2883
- const rows6 = (await db.query({ [ns]: {} }))[ns] ?? [];
2884
- return rows6.some((row) => Object.entries(where).every(([key, value]) => row[key] === value));
2883
+ const rows5 = (await db.query({ [ns]: {} }))[ns] ?? [];
2884
+ return rows5.some((row) => Object.entries(where).every(([key, value]) => row[key] === value));
2885
2885
  }
2886
2886
  async function loadActiveMemberContentControl(db, targetId, environment) {
2887
2887
  const where = { targetId, environment, active: true };
2888
2888
  if (!await exists(db, "memberContentControls", where)) return null;
2889
- const rows6 = (await db.query({ memberContentControls: { $: {
2889
+ const rows5 = (await db.query({ memberContentControls: { $: {
2890
2890
  where,
2891
2891
  order: { revision: "desc" },
2892
2892
  limit: 1
2893
2893
  } } })).memberContentControls;
2894
- return control(Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
2894
+ return control(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
2895
2895
  }
2896
2896
  async function loadLatestMemberContentControl(db, targetId, environment) {
2897
2897
  const where = { targetId, environment };
2898
2898
  if (!await exists(db, "memberContentControls", where)) return null;
2899
- const rows6 = (await db.query({ memberContentControls: { $: {
2899
+ const rows5 = (await db.query({ memberContentControls: { $: {
2900
2900
  where,
2901
2901
  order: { revision: "desc" },
2902
2902
  limit: 1
2903
2903
  } } })).memberContentControls;
2904
- return control(Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
2904
+ return control(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
2905
2905
  }
2906
2906
  async function loadLocalMemberContent(db, chapterId, environment) {
2907
2907
  const id = `member-content-local:${environment}`;
2908
- const rows6 = (await db.query({ memberContentLocal: { $: { where: { id }, limit: 1 } } })).memberContentLocal;
2909
- return local(Array.isArray(rows6) ? rows6[0] : void 0);
2908
+ const rows5 = (await db.query({ memberContentLocal: { $: { where: { id }, limit: 1 } } })).memberContentLocal;
2909
+ return local(Array.isArray(rows5) ? rows5[0] : void 0);
2910
2910
  }
2911
2911
  async function materializeMemberContentControl(db, envelope, previous, receivedAt) {
2912
2912
  const { document, digest: digest2 } = envelope;
@@ -2961,12 +2961,12 @@ async function groupAndTiers(db, chapterId) {
2961
2961
  }
2962
2962
  async function memberTierId(db, chapterId, email) {
2963
2963
  if (!email) return null;
2964
- let rows6 = (await db.query({ applications: { $: {
2964
+ let rows5 = (await db.query({ applications: { $: {
2965
2965
  where: { emailNormalized: normalizeAdmissionEmail(email) },
2966
2966
  limit: 20
2967
2967
  } } })).applications;
2968
- if (!rows6?.length) rows6 = (await db.query({ applications: { $: { where: { email }, limit: 20 } } })).applications;
2969
- 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;
2968
+ if (!rows5?.length) rows5 = (await db.query({ applications: { $: { where: { email }, limit: 20 } } })).applications;
2969
+ 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;
2970
2970
  return typeof latest?.tierId === "string" && latest.tierId ? latest.tierId : null;
2971
2971
  }
2972
2972
  async function activeDocuments(db, chapterId, environment) {
@@ -3692,8 +3692,8 @@ function makeCalendar(env) {
3692
3692
  }
3693
3693
  async function firstRow(db, ns, q) {
3694
3694
  const res = await db.query({ [ns]: { $: q } });
3695
- const rows6 = res[ns];
3696
- return Array.isArray(rows6) ? rows6[0] : void 0;
3695
+ const rows5 = res[ns];
3696
+ return Array.isArray(rows5) ? rows5[0] : void 0;
3697
3697
  }
3698
3698
  async function computeSlots(cal, cfg) {
3699
3699
  const { from, to } = slotWindow(Date.now(), cfg.windowDays);
@@ -4093,8 +4093,8 @@ function createStripeProvider(config) {
4093
4093
  const sk = requireSecret(secretKey);
4094
4094
  const charges = await stripeCall(sk, "GET", "/v1/charges", { customer: input.customerId, limit: 100 });
4095
4095
  if (!charges.ok) fail("charges list", charges);
4096
- const rows6 = charges.body.data ?? [];
4097
- const paid = rows6.filter((c) => c.status === "succeeded" && c.refunded !== true);
4096
+ const rows5 = charges.body.data ?? [];
4097
+ const paid = rows5.filter((c) => c.status === "succeeded" && c.refunded !== true);
4098
4098
  const charge = paid[paid.length - 1];
4099
4099
  if (!charge) {
4100
4100
  const err = new Error("no paid charge to refund");
@@ -4202,8 +4202,8 @@ async function issueGiftClaim(db, intent, provider, now) {
4202
4202
  throw new Error("gift issuance conflict");
4203
4203
  }
4204
4204
  }
4205
- function buyerGiftClaims(rows6, now) {
4206
- return rows6.map((row) => {
4205
+ function buyerGiftClaims(rows5, now) {
4206
+ return rows5.map((row) => {
4207
4207
  const stored = String(row.status ?? "pending");
4208
4208
  const status = stored === "pending" && Number(row.codeExpiresAt) <= now ? "expired" : stored;
4209
4209
  return {
@@ -4495,8 +4495,8 @@ function classifyMemberStripeEvent(identity, target, hasProvenLegacyLocalLink) {
4495
4495
 
4496
4496
  // src/stripe-webhook-partition.ts
4497
4497
  var firstRow2 = async (db, ns, q) => {
4498
- const rows6 = (await db.query({ [ns]: { $: q } }))[ns];
4499
- return Array.isArray(rows6) ? rows6[0] : void 0;
4498
+ const rows5 = (await db.query({ [ns]: { $: q } }))[ns];
4499
+ return Array.isArray(rows5) ? rows5[0] : void 0;
4500
4500
  };
4501
4501
  var stripeEventDigest = async (rawBody) => {
4502
4502
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(rawBody));
@@ -4566,8 +4566,8 @@ async function replayedStripeReceipt(db, eventId, digest2, event) {
4566
4566
  // src/worker-routes-payments.ts
4567
4567
  async function firstRow3(db, ns, q) {
4568
4568
  const res = await db.query({ [ns]: { $: q } });
4569
- const rows6 = res[ns];
4570
- return Array.isArray(rows6) ? rows6[0] : void 0;
4569
+ const rows5 = res[ns];
4570
+ return Array.isArray(rows5) ? rows5[0] : void 0;
4571
4571
  }
4572
4572
  async function findApplication(db, event) {
4573
4573
  if ("applicationId" in event && event.applicationId) {
@@ -5033,8 +5033,8 @@ async function upcomingEvents(env) {
5033
5033
  const res = await cal.availability.upcoming();
5034
5034
  return res.events.map((e) => ({ eventId: e.eventId, status: e.status, startAt: e.startAt, endAt: e.endAt }));
5035
5035
  }
5036
- function toReconcile(rows6) {
5037
- return rows6.map((m) => ({
5036
+ function toReconcile(rows5) {
5037
+ return rows5.map((m) => ({
5038
5038
  id: String(m.id),
5039
5039
  applicationId: String(m.applicationId),
5040
5040
  googleEventId: typeof m.googleEventId === "string" ? m.googleEventId : null,
@@ -5056,12 +5056,12 @@ var handleAdminMeetings = async (req, url, env, ctx) => {
5056
5056
  const query = await db.query({
5057
5057
  meetings: { $: { where: all ? {} : { status: "scheduled" }, order: { startAt: "asc" }, limit: 500 } }
5058
5058
  });
5059
- let rows6 = Array.isArray(query.meetings) ? query.meetings : [];
5060
- if (Number.isFinite(from)) rows6 = rows6.filter((m) => Number(m.startAt ?? 0) >= from);
5061
- if (Number.isFinite(to)) rows6 = rows6.filter((m) => Number(m.startAt ?? 0) <= to);
5059
+ let rows5 = Array.isArray(query.meetings) ? query.meetings : [];
5060
+ if (Number.isFinite(from)) rows5 = rows5.filter((m) => Number(m.startAt ?? 0) >= from);
5061
+ if (Number.isFinite(to)) rows5 = rows5.filter((m) => Number(m.startAt ?? 0) <= to);
5062
5062
  let decisions = [];
5063
5063
  try {
5064
- decisions = reconcileMeetings(toReconcile(rows6), await upcomingEvents(env), Date.now());
5064
+ decisions = reconcileMeetings(toReconcile(rows5), await upcomingEvents(env), Date.now());
5065
5065
  } catch {
5066
5066
  }
5067
5067
  const patched = /* @__PURE__ */ new Map();
@@ -5088,7 +5088,7 @@ var handleAdminMeetings = async (req, url, env, ctx) => {
5088
5088
  };
5089
5089
  const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
5090
5090
  const timezone = resolveScheduling(group?.schedulingJson).timezone;
5091
- const meetings = rows6.map((m) => ({ ...m, ...patched.get(String(m.id)) ?? {}, applicant: applicantOf(m) })).filter((m) => all || m.status === "scheduled");
5091
+ const meetings = rows5.map((m) => ({ ...m, ...patched.get(String(m.id)) ?? {}, applicant: applicantOf(m) })).filter((m) => all || m.status === "scheduled");
5092
5092
  return json({ meetings, adopted: decisions.length, timezone });
5093
5093
  };
5094
5094
 
@@ -5164,10 +5164,10 @@ var handleAdminPeople = async (req, url, env, ctx) => {
5164
5164
  });
5165
5165
  }
5166
5166
  }
5167
- const rows6 = [...people.values()].sort(
5167
+ const rows5 = [...people.values()].sort(
5168
5168
  (x, y) => (y.application?.createdAt ?? -1) - (x.application?.createdAt ?? -1)
5169
5169
  );
5170
- return json({ people: rows6 });
5170
+ return json({ people: rows5 });
5171
5171
  };
5172
5172
  var handleAdminPeopleAccess = async (req, url, env, ctx) => {
5173
5173
  if (req.method !== "GET" || url.pathname !== "/api/admin/people/access") return null;
@@ -7033,7 +7033,7 @@ function canonicalNetworkPersonId(personId, persons) {
7033
7033
  }
7034
7034
  }
7035
7035
 
7036
- // src/network-person-store.ts
7036
+ // src/network-person-store-state.ts
7037
7037
  var NetworkPersonRegistryError = class extends Error {
7038
7038
  constructor(code, message) {
7039
7039
  super(message);
@@ -7041,29 +7041,112 @@ var NetworkPersonRegistryError = class extends Error {
7041
7041
  }
7042
7042
  code;
7043
7043
  };
7044
- var rows4 = (value) => Array.isArray(value) ? value : [];
7045
- async function loadNetworkPersonRegistry(db) {
7046
- const result = await db.query({
7047
- networkPersons: { $: { order: { updatedAt: "desc" }, limit: 1e3 } },
7048
- networkIdentityClaims: { $: { limit: 1e3 } },
7049
- networkPersonAliases: { $: { order: { updatedAt: "desc" }, limit: 1e3 } },
7050
- networkChapterPersonLinks: { $: { limit: 1e3 } },
7051
- networkPersonEvents: { $: { order: { createdAt: "desc" }, limit: 1e3 } }
7052
- });
7053
- return {
7054
- persons: rows4(result.networkPersons),
7055
- claims: rows4(result.networkIdentityClaims),
7056
- aliases: rows4(result.networkPersonAliases),
7057
- links: rows4(result.networkChapterPersonLinks),
7058
- events: rows4(result.networkPersonEvents)
7059
- };
7060
- }
7061
- var eventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
7044
+ var networkPersonEventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
7062
7045
  t: "update",
7063
7046
  ns: "networkPersonEvents",
7064
7047
  id: `network_person_event:${mutationId}`,
7065
7048
  attrs: { id: `network_person_event:${mutationId}`, kind, actorId: actor.id, ...ids, mutationId, detail, createdAt: now }
7066
7049
  });
7050
+ var asRows = (value) => Array.isArray(value) ? value : [];
7051
+ var MAX_ROWS = 1e4;
7052
+ async function boundedRows(db, ns, order2) {
7053
+ const collected = [];
7054
+ const pageSize = 500;
7055
+ for (let offset = 0; offset <= MAX_ROWS; offset += pageSize) {
7056
+ const result = await db.query({ [ns]: { $: { limit: pageSize, offset, ...order2 ? { order: order2 } : {} } } });
7057
+ const page = asRows(result[ns]);
7058
+ collected.push(...page);
7059
+ if (collected.length > MAX_ROWS) {
7060
+ throw new NetworkPersonRegistryError(
7061
+ "registry_too_large",
7062
+ `network Person registry ${ns} exceeds the ${MAX_ROWS.toLocaleString()}-row operational bound`
7063
+ );
7064
+ }
7065
+ if (page.length < pageSize) return collected;
7066
+ }
7067
+ return collected;
7068
+ }
7069
+ async function loadNetworkPersonRegistry(db) {
7070
+ const [persons, claims, aliases, links, events] = await Promise.all([
7071
+ boundedRows(db, "networkPersons", { updatedAt: "desc" }),
7072
+ boundedRows(db, "networkIdentityClaims"),
7073
+ boundedRows(db, "networkPersonAliases", { updatedAt: "desc" }),
7074
+ boundedRows(db, "networkChapterPersonLinks"),
7075
+ boundedRows(db, "networkPersonEvents", { createdAt: "desc" })
7076
+ ]);
7077
+ return { persons, claims, aliases, links, events };
7078
+ }
7079
+
7080
+ // src/network-person-link-store.ts
7081
+ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date.now()) {
7082
+ const state = await loadNetworkPersonRegistry(db);
7083
+ const canonicalId = canonicalNetworkPersonId(input.personId, state.persons);
7084
+ const person = state.persons.find((row) => row.id === input.personId);
7085
+ if (!person || person.state !== "active" || canonicalId !== person.id) {
7086
+ throw new NetworkPersonRegistryError("person_not_active", "link target must be an active canonical Person");
7087
+ }
7088
+ const sourceKey = await networkChapterPersonSourceKey(input.chapterId, input.chapterPersonId);
7089
+ const existingSource = state.links.find((link2) => link2.state === "shadow" && (link2.sourceKey === sourceKey || link2.chapterId === input.chapterId && link2.chapterPersonId === input.chapterPersonId));
7090
+ if (existingSource) {
7091
+ if (existingSource.personId !== person.id) {
7092
+ throw new NetworkPersonRegistryError("source_already_linked", "chapter source row is linked to another Person");
7093
+ }
7094
+ return { person, link: existingSource, replayed: true };
7095
+ }
7096
+ if (person.revision !== input.expectedPersonRevision) {
7097
+ throw new NetworkPersonRegistryError("stale_source_link", "Person changed; reload before linking this source row");
7098
+ }
7099
+ const expectedClaim = await networkIdentityClaim({ kind: "email", value: input.verifiedEmail, verifiedAt: 1 });
7100
+ if (!state.claims.some((claim) => claim.id === expectedClaim.id && claim.personId === person.id && claim.kind === "email")) {
7101
+ throw new NetworkPersonRegistryError("identity_mismatch", "source email is not an exact verified identity on this Person");
7102
+ }
7103
+ const id = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
7104
+ const previous = state.links.find((link2) => link2.id === id);
7105
+ const link = {
7106
+ id,
7107
+ personId: person.id,
7108
+ chapterId: input.chapterId,
7109
+ chapterPersonId: input.chapterPersonId,
7110
+ sourceKey,
7111
+ state: "shadow",
7112
+ createdAt: previous?.createdAt ?? now
7113
+ };
7114
+ const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
7115
+ await db.transact([
7116
+ { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
7117
+ { t: "update", ns: "networkChapterPersonLinks", id, attrs: { ...link } },
7118
+ networkPersonEventOp("chapter_source_linked", mutationId, actor, now, { chapterId: input.chapterId }, { personId: person.id })
7119
+ ], { mutationId, guards: [
7120
+ { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
7121
+ { ns: "networkChapterPersonLinks", id, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
7122
+ ] });
7123
+ return { person: nextPerson, link };
7124
+ }
7125
+ async function unlinkNetworkChapterPerson(db, input, mutationId, actor, now = Date.now()) {
7126
+ const reason = input.reason.trim();
7127
+ if (!reason || reason.length > 500) throw new NetworkPersonRegistryError("invalid_unlink", "a bounded unlink reason is required");
7128
+ const state = await loadNetworkPersonRegistry(db);
7129
+ const person = state.persons.find((row) => row.id === input.personId);
7130
+ const link = state.links.find((row) => row.id === input.linkId && row.personId === input.personId);
7131
+ if (!person || person.state !== "active" || person.revision !== input.expectedPersonRevision || !link || link.state !== "shadow") {
7132
+ throw new NetworkPersonRegistryError("stale_source_unlink", "Person or source link changed; reload before undoing the link");
7133
+ }
7134
+ const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
7135
+ const nextLink = { ...link, state: "revoked" };
7136
+ delete nextLink.sourceKey;
7137
+ await db.transact([
7138
+ { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
7139
+ { t: "update", ns: "networkChapterPersonLinks", id: link.id, attrs: { state: "revoked" } },
7140
+ { t: "retract", ns: "networkChapterPersonLinks", id: link.id, attrs: ["sourceKey"] },
7141
+ networkPersonEventOp("chapter_source_unlinked", mutationId, actor, now, { chapterId: link.chapterId, reason }, { personId: person.id })
7142
+ ], { mutationId, guards: [
7143
+ { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
7144
+ { ns: "networkChapterPersonLinks", id: link.id, exists: true, equals: { state: "shadow", personId: person.id } }
7145
+ ] });
7146
+ return { person: nextPerson, link: nextLink };
7147
+ }
7148
+
7149
+ // src/network-person-store.ts
7067
7150
  var defaultPolicy = (now, consent) => ({
7068
7151
  marketingSuppressed: consent.marketing !== "granted",
7069
7152
  discretionarySuppressed: consent.discretionary !== "granted",
@@ -7073,7 +7156,8 @@ var isGuardConflict = (error) => error instanceof Error && error.code === "trans
7073
7156
  async function linkOps(personId, links, now) {
7074
7157
  return Promise.all(links.map(async (link) => {
7075
7158
  const id = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
7076
- return { t: "update", ns: "networkChapterPersonLinks", id, attrs: { id, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, state: "shadow", createdAt: now } };
7159
+ const sourceKey = await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId);
7160
+ return { t: "update", ns: "networkChapterPersonLinks", id, attrs: { id, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
7077
7161
  }));
7078
7162
  }
7079
7163
  async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
@@ -7095,7 +7179,7 @@ async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId
7095
7179
  };
7096
7180
  await db.transact([
7097
7181
  { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...alias } },
7098
- eventOp("import_queued_ambiguous", mutationId, actor, now, { candidateCount: candidates.length, sourceSystem: input.sourceSystem }, { aliasId })
7182
+ networkPersonEventOp("import_queued_ambiguous", mutationId, actor, now, { candidateCount: candidates.length, sourceSystem: input.sourceSystem }, { aliasId })
7099
7183
  ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: false }] });
7100
7184
  return { status: "ambiguous", alias };
7101
7185
  }
@@ -7131,7 +7215,7 @@ async function importNetworkPerson(db, input, mutationId, actor, now = Date.now(
7131
7215
  { t: "update", ns: "networkPersonAliases", id: aliasId, attrs: { ...alias } },
7132
7216
  ...claimOps,
7133
7217
  ...await linkOps(personId, input.chapterLinks ?? [], now),
7134
- eventOp("person_imported", mutationId, actor, now, { sourceSystem: input.sourceSystem, identityCount: claims.length }, { personId, aliasId })
7218
+ networkPersonEventOp("person_imported", mutationId, actor, now, { sourceSystem: input.sourceSystem, identityCount: claims.length }, { personId, aliasId })
7135
7219
  ];
7136
7220
  try {
7137
7221
  await db.transact(ops, { mutationId, guards: [
@@ -7153,12 +7237,21 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
7153
7237
  const state = await loadNetworkPersonRegistry(db);
7154
7238
  const alias = state.aliases.find((row) => row.id === input.aliasId);
7155
7239
  if (!alias) throw new NetworkPersonRegistryError("alias_not_found", "source alias not found");
7156
- if (alias.revision !== input.expectedRevision || alias.status !== "ambiguous") throw new NetworkPersonRegistryError("stale_review", "source alias changed; reload before reviewing");
7240
+ const expectedStatus = input.decision === "reopen" ? "excluded" : "ambiguous";
7241
+ if (alias.revision !== input.expectedRevision || alias.status !== expectedStatus) throw new NetworkPersonRegistryError("stale_review", "source alias changed; reload before reviewing");
7242
+ if (input.decision === "reopen") {
7243
+ const next2 = { ...alias, status: "ambiguous", revision: alias.revision + 1, updatedAt: now };
7244
+ await db.transact([
7245
+ { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...next2 } },
7246
+ networkPersonEventOp("alias_reopened", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7247
+ ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "excluded" } }] });
7248
+ return next2;
7249
+ }
7157
7250
  if (input.decision === "exclude") {
7158
7251
  const next2 = { ...alias, status: "excluded", revision: alias.revision + 1, updatedAt: now };
7159
7252
  await db.transact([
7160
7253
  { t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...next2 } },
7161
- eventOp("alias_excluded", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7254
+ networkPersonEventOp("alias_excluded", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
7162
7255
  ], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } }] });
7163
7256
  return next2;
7164
7257
  }
@@ -7174,7 +7267,7 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
7174
7267
  { t: "retract", ns: "networkPersonAliases", id: alias.id, attrs: ["candidatePersonIds"] },
7175
7268
  ...missing.map((claim) => ({ t: "update", ns: "networkIdentityClaims", id: claim.id, attrs: { ...claim, personId: canonicalId, createdAt: now } })),
7176
7269
  ...await linkOps(canonicalId, alias.chapterLinks, now),
7177
- eventOp("alias_linked", mutationId, actor, now, { identityCount: alias.identityClaims.length }, { personId: canonicalId, aliasId: alias.id })
7270
+ networkPersonEventOp("alias_linked", mutationId, actor, now, { identityCount: alias.identityClaims.length }, { personId: canonicalId, aliasId: alias.id })
7178
7271
  ], { mutationId, guards: [
7179
7272
  { ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } },
7180
7273
  ...missing.map((claim) => ({ ns: "networkIdentityClaims", id: claim.id, exists: false }))
@@ -7204,7 +7297,7 @@ async function mergeNetworkPersons(db, input, mutationId, actor, now = Date.now(
7204
7297
  await db.transact([
7205
7298
  { t: "update", ns: "networkPersons", id: source.id, attrs: { ...sourceNext } },
7206
7299
  { t: "update", ns: "networkPersons", id: target.id, attrs: { ...targetNext } },
7207
- eventOp("persons_merged", mutationId, actor, now, { targetPersonId: target.id }, { personId: source.id })
7300
+ networkPersonEventOp("persons_merged", mutationId, actor, now, { targetPersonId: target.id }, { personId: source.id })
7208
7301
  ], { mutationId, guards: [
7209
7302
  { ns: "networkPersons", id: source.id, exists: true, equals: { revision: source.revision, state: "active" } },
7210
7303
  { ns: "networkPersons", id: target.id, exists: true, equals: { revision: target.revision, state: "active" } }
@@ -7220,7 +7313,7 @@ async function unmergeNetworkPerson(db, input, mutationId, actor, now = Date.now
7220
7313
  await db.transact([
7221
7314
  { t: "update", ns: "networkPersons", id: person.id, attrs: { state: "active", revision: next.revision, updatedAt: now } },
7222
7315
  { t: "retract", ns: "networkPersons", id: person.id, attrs: ["mergedIntoId"] },
7223
- eventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
7316
+ networkPersonEventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
7224
7317
  ], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "merged", mergedIntoId: person.mergedIntoId } }] });
7225
7318
  return next;
7226
7319
  }
@@ -7234,50 +7327,16 @@ async function suppressNetworkPerson(db, input, mutationId, actor, now = Date.no
7234
7327
  const next = { ...person, contactPolicy: policy, revision: person.revision + 1, updatedAt: now };
7235
7328
  await db.transact([
7236
7329
  { t: "update", ns: "networkPersons", id: person.id, attrs: { ...next } },
7237
- eventOp("contact_suppression_changed", mutationId, actor, now, { marketingSuppressed: policy.marketingSuppressed, discretionarySuppressed: policy.discretionarySuppressed }, { personId: person.id })
7330
+ networkPersonEventOp("contact_suppression_changed", mutationId, actor, now, { marketingSuppressed: policy.marketingSuppressed, discretionarySuppressed: policy.discretionarySuppressed }, { personId: person.id })
7238
7331
  ], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision } }] });
7239
7332
  return next;
7240
7333
  }
7241
7334
 
7242
- // src/network-person-reveal.ts
7243
- async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, now = Date.now()) {
7244
- const reason = input.reason.trim();
7245
- if (!reason || reason.length > 500) {
7246
- throw new NetworkPersonRegistryError("invalid_reveal", "a bounded reveal reason is required");
7247
- }
7248
- const state = await loadNetworkPersonRegistry(db);
7249
- const target = input.kind === "person" ? state.persons.find((row) => row.id === input.id) : state.aliases.find((row) => row.id === input.id);
7250
- if (!target) throw new NetworkPersonRegistryError("reveal_not_found", "registry record not found");
7251
- const ids = input.kind === "person" ? { personId: input.id } : { aliasId: input.id };
7252
- const event = {
7253
- t: "update",
7254
- ns: "networkPersonEvents",
7255
- id: `network_person_event:${mutationId}`,
7256
- attrs: {
7257
- id: `network_person_event:${mutationId}`,
7258
- kind: "pii_revealed",
7259
- actorId: actor.id,
7260
- ...ids,
7261
- mutationId,
7262
- detail: { targetKind: input.kind, reason },
7263
- createdAt: now
7264
- }
7265
- };
7266
- await db.transact([event], {
7267
- mutationId,
7268
- guards: [{ ns: "networkPersonEvents", id: `network_person_event:${mutationId}`, exists: false }]
7269
- });
7270
- return input.kind === "person" ? { kind: "person", person: target, identities: state.claims.filter((claim) => claim.personId === input.id) } : { kind: "alias", alias: target };
7271
- }
7272
-
7273
- // src/worker-routes-network-person-registry.ts
7274
- var PATH2 = "/api/admin/network-person-registry";
7275
- var MAX_BODY2 = 32e3;
7276
- var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
7335
+ // src/network-person-route-projection.ts
7277
7336
  function redactedName(displayName) {
7278
7337
  return displayName.split(/\s+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}\u2026`).join(" ");
7279
7338
  }
7280
- var redactedLink = async (link) => ({
7339
+ var redactedNetworkPersonLink = async (link) => ({
7281
7340
  id: link.id,
7282
7341
  personId: link.personId,
7283
7342
  chapterId: link.chapterId,
@@ -7285,13 +7344,13 @@ var redactedLink = async (link) => ({
7285
7344
  state: link.state,
7286
7345
  createdAt: link.createdAt
7287
7346
  });
7288
- var safePolicy = (policy) => ({
7347
+ var safeNetworkContactPolicy = (policy) => ({
7289
7348
  marketingSuppressed: policy.marketingSuppressed,
7290
7349
  discretionarySuppressed: policy.discretionarySuppressed,
7291
7350
  hasReason: Boolean(policy.reason),
7292
7351
  updatedAt: policy.updatedAt
7293
7352
  });
7294
- var safeAlias = (alias) => ({
7353
+ var safeNetworkPersonAlias = async (alias) => ({
7295
7354
  id: alias.id,
7296
7355
  sourceSystem: alias.sourceSystem,
7297
7356
  displayNameHint: redactedName(alias.displayName),
@@ -7306,17 +7365,91 @@ var safeAlias = (alias) => ({
7306
7365
  },
7307
7366
  identityClaims: alias.identityClaims.map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7308
7367
  chapterIds: [...new Set(alias.chapterLinks.map((link) => link.chapterId))],
7368
+ chapterSources: await Promise.all(alias.chapterLinks.map(async (link) => ({
7369
+ chapterId: link.chapterId,
7370
+ sourceKey: await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId)
7371
+ }))),
7309
7372
  revision: alias.revision,
7310
7373
  updatedAt: alias.updatedAt
7311
7374
  });
7312
- var safePerson = (person) => ({
7375
+ var safeNetworkPerson = (person, claims = []) => ({
7313
7376
  id: person.id,
7377
+ displayName: person.displayName,
7314
7378
  displayNameHint: redactedName(person.displayName),
7379
+ 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)),
7315
7380
  state: person.state,
7316
7381
  mergedIntoId: person.mergedIntoId ?? null,
7317
7382
  revision: person.revision,
7318
- contactPolicy: safePolicy(person.contactPolicy)
7383
+ contactPolicy: safeNetworkContactPolicy(person.contactPolicy)
7319
7384
  });
7385
+ async function projectNetworkPersonRegistryState(state) {
7386
+ const persons = await Promise.all(state.persons.map(async (person) => {
7387
+ const claims = state.claims.filter((claim) => claim.personId === person.id);
7388
+ const links = state.links.filter((link) => link.personId === person.id && link.state === "shadow");
7389
+ const safeLinks = await Promise.all(links.map(redactedNetworkPersonLink));
7390
+ const canonicalId = canonicalNetworkPersonId(person.id, state.persons);
7391
+ const canonical2 = state.persons.find((candidate) => candidate.id === canonicalId) ?? person;
7392
+ return {
7393
+ ...safeNetworkPerson(person, claims),
7394
+ canonicalPersonId: canonicalId,
7395
+ identityClaims: claims.map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7396
+ chapterLinks: safeLinks,
7397
+ chapterProjections: projectNetworkContactPolicy(canonical2.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safeNetworkContactPolicy(contactPolicy) }))
7398
+ };
7399
+ }));
7400
+ const aliases = await Promise.all(state.aliases.map(safeNetworkPersonAlias));
7401
+ return {
7402
+ persons,
7403
+ aliases,
7404
+ reviewQueue: aliases.filter((alias) => alias.status === "ambiguous"),
7405
+ excluded: aliases.filter((alias) => alias.status === "excluded"),
7406
+ recentEvents: state.events.slice(0, 100).map((event) => ({
7407
+ id: event.id,
7408
+ kind: event.kind,
7409
+ personId: event.personId ?? null,
7410
+ aliasId: event.aliasId ?? null,
7411
+ actorId: event.actorId,
7412
+ mutationId: event.mutationId,
7413
+ createdAt: event.createdAt
7414
+ }))
7415
+ };
7416
+ }
7417
+
7418
+ // src/network-person-reveal.ts
7419
+ async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, now = Date.now()) {
7420
+ const reason = input.reason.trim();
7421
+ if (!reason || reason.length > 500) {
7422
+ throw new NetworkPersonRegistryError("invalid_reveal", "a bounded reveal reason is required");
7423
+ }
7424
+ const state = await loadNetworkPersonRegistry(db);
7425
+ const target = input.kind === "person" ? state.persons.find((row) => row.id === input.id) : state.aliases.find((row) => row.id === input.id);
7426
+ if (!target) throw new NetworkPersonRegistryError("reveal_not_found", "registry record not found");
7427
+ const ids = input.kind === "person" ? { personId: input.id } : { aliasId: input.id };
7428
+ const event = {
7429
+ t: "update",
7430
+ ns: "networkPersonEvents",
7431
+ id: `network_person_event:${mutationId}`,
7432
+ attrs: {
7433
+ id: `network_person_event:${mutationId}`,
7434
+ kind: "pii_revealed",
7435
+ actorId: actor.id,
7436
+ ...ids,
7437
+ mutationId,
7438
+ detail: { targetKind: input.kind, reason },
7439
+ createdAt: now
7440
+ }
7441
+ };
7442
+ await db.transact([event], {
7443
+ mutationId,
7444
+ guards: [{ ns: "networkPersonEvents", id: `network_person_event:${mutationId}`, exists: false }]
7445
+ });
7446
+ return input.kind === "person" ? { kind: "person", person: target, identities: state.claims.filter((claim) => claim.personId === input.id) } : { kind: "alias", alias: target };
7447
+ }
7448
+
7449
+ // src/worker-routes-network-person-registry.ts
7450
+ var PATH2 = "/api/admin/network-person-registry";
7451
+ var MAX_BODY2 = 32e3;
7452
+ var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
7320
7453
  var positiveRevision = (value) => {
7321
7454
  const parsed = Number(value);
7322
7455
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
@@ -7372,14 +7505,14 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7372
7505
  return json({
7373
7506
  result: {
7374
7507
  status: result.status,
7375
- alias: safeAlias(result.alias),
7376
- ..."person" in result && result.person ? { person: safePerson(result.person) } : {},
7508
+ alias: await safeNetworkPersonAlias(result.alias),
7509
+ ..."person" in result && result.person ? { person: safeNetworkPerson(result.person, result.alias.identityClaims) } : {},
7377
7510
  ..."replayed" in result ? { replayed: result.replayed } : {}
7378
7511
  }
7379
7512
  }, 201);
7380
7513
  }
7381
7514
  if (command === "review") {
7382
- if (input.decision !== "link" && input.decision !== "exclude") return json({ error: "review decision must be link or exclude" }, 400);
7515
+ if (input.decision !== "link" && input.decision !== "exclude" && input.decision !== "reopen") return json({ error: "review decision must be link, exclude, or reopen" }, 400);
7383
7516
  const expectedRevision = positiveRevision(input.expectedRevision);
7384
7517
  if (!expectedRevision) return json({ error: "expectedRevision is required" }, 400);
7385
7518
  if (input.decision === "link" && typeof input.personId !== "string") return json({ error: "personId is required when linking" }, 400);
@@ -7389,7 +7522,42 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7389
7522
  decision: input.decision,
7390
7523
  ...typeof input.personId === "string" ? { personId: input.personId } : {}
7391
7524
  }, mutationId, actionActor);
7392
- return json({ result: safeAlias(result) });
7525
+ return json({ result: await safeNetworkPersonAlias(result) });
7526
+ }
7527
+ if (command === "link-source") {
7528
+ const expectedPersonRevision = positiveRevision(input.expectedPersonRevision);
7529
+ const chapterId = typeof input.chapterId === "string" ? input.chapterId : "";
7530
+ const chapterPersonId = typeof input.chapterPersonId === "string" ? input.chapterPersonId.trim() : "";
7531
+ const verifiedEmail = typeof input.verifiedEmail === "string" ? input.verifiedEmail.trim() : "";
7532
+ 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);
7533
+ const result = await linkNetworkChapterPerson(db, {
7534
+ personId: typeof input.personId === "string" ? input.personId : "",
7535
+ expectedPersonRevision,
7536
+ chapterId,
7537
+ chapterPersonId,
7538
+ verifiedEmail
7539
+ }, mutationId, actionActor);
7540
+ return json({
7541
+ result: {
7542
+ person: safeNetworkPerson(result.person),
7543
+ link: await redactedNetworkPersonLink(result.link),
7544
+ ...result.replayed ? { replayed: true } : {}
7545
+ }
7546
+ });
7547
+ }
7548
+ if (command === "unlink-source") {
7549
+ const expectedPersonRevision = positiveRevision(input.expectedPersonRevision);
7550
+ const reason = typeof input.reason === "string" ? input.reason.trim() : "";
7551
+ if (!expectedPersonRevision || !reason || reason.length > 500) {
7552
+ return json({ error: "a Person revision and bounded unlink reason are required" }, 400);
7553
+ }
7554
+ const result = await unlinkNetworkChapterPerson(db, {
7555
+ personId: typeof input.personId === "string" ? input.personId : "",
7556
+ linkId: typeof input.linkId === "string" ? input.linkId : "",
7557
+ expectedPersonRevision,
7558
+ reason
7559
+ }, mutationId, actionActor);
7560
+ return json({ result: { person: safeNetworkPerson(result.person), linkState: result.link.state } });
7393
7561
  }
7394
7562
  if (command === "merge") {
7395
7563
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7401,7 +7569,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7401
7569
  expectedRevision,
7402
7570
  expectedTargetRevision
7403
7571
  }, mutationId, actionActor);
7404
- return json({ result: { source: safePerson(result.source), target: safePerson(result.target) } });
7572
+ return json({ result: { source: safeNetworkPerson(result.source), target: safeNetworkPerson(result.target) } });
7405
7573
  }
7406
7574
  if (command === "unmerge") {
7407
7575
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7410,7 +7578,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7410
7578
  personId: typeof input.personId === "string" ? input.personId : "",
7411
7579
  expectedRevision
7412
7580
  }, mutationId, actionActor);
7413
- return json({ result: safePerson(result) });
7581
+ return json({ result: safeNetworkPerson(result) });
7414
7582
  }
7415
7583
  if (command === "suppression") {
7416
7584
  const expectedRevision = positiveRevision(input.expectedRevision);
@@ -7426,7 +7594,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7426
7594
  discretionarySuppressed: input.discretionarySuppressed,
7427
7595
  ...reason ? { reason } : {}
7428
7596
  }, mutationId, actionActor);
7429
- return json({ result: safePerson(result) });
7597
+ return json({ result: safeNetworkPerson(result) });
7430
7598
  }
7431
7599
  if (command === "reveal") {
7432
7600
  if (input.kind !== "person" && input.kind !== "alias") return json({ error: "reveal kind must be person or alias" }, 400);
@@ -7445,48 +7613,19 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
7445
7613
  }
7446
7614
  }
7447
7615
  const state = await loadNetworkPersonRegistry(db);
7448
- const persons = await Promise.all(state.persons.map(async (person) => {
7449
- const links = state.links.filter((link) => link.personId === person.id);
7450
- const safeLinks = await Promise.all(links.map(redactedLink));
7451
- const canonicalId = canonicalNetworkPersonId(person.id, state.persons);
7452
- const canonical2 = state.persons.find((candidate) => candidate.id === canonicalId) ?? person;
7453
- return {
7454
- id: person.id,
7455
- displayNameHint: redactedName(person.displayName),
7456
- state: person.state,
7457
- canonicalPersonId: canonicalId,
7458
- mergedIntoId: person.mergedIntoId ?? null,
7459
- revision: person.revision,
7460
- contactPolicy: safePolicy(person.contactPolicy),
7461
- identityClaims: state.claims.filter((claim) => claim.personId === person.id).map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
7462
- chapterLinks: safeLinks,
7463
- chapterProjections: projectNetworkContactPolicy(canonical2.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safePolicy(contactPolicy) }))
7464
- };
7465
- }));
7466
- const aliases = state.aliases.map(safeAlias);
7616
+ const projection = await projectNetworkPersonRegistryState(state);
7467
7617
  return json({
7468
7618
  version: 1,
7469
7619
  mode: "shadow",
7470
7620
  writable: true,
7471
7621
  authoritativeForChapterOperation: false,
7472
- // loadNetworkPersonRegistry deliberately bounds each collection at 1,000.
7473
- // Exactly 1,000 links may mean there are more, so fail closed rather than
7474
- // letting a consumer mistake an omitted explicit link for "source only".
7475
- chapterLinkProjectionComplete: state.links.length < 1e3,
7622
+ // The store pages every collection to its explicit 10,000-row operational
7623
+ // bound and rejects larger state instead of returning a partial projection.
7624
+ chapterLinkProjectionComplete: true,
7625
+ personProjectionComplete: true,
7626
+ aliasProjectionComplete: true,
7476
7627
  configuredChapterIds: ctx.chapter.networkPersonRegistry.chapterIds,
7477
- persons,
7478
- aliases,
7479
- reviewQueue: aliases.filter((alias) => alias.status === "ambiguous"),
7480
- excluded: aliases.filter((alias) => alias.status === "excluded"),
7481
- recentEvents: state.events.slice(0, 100).map((event) => ({
7482
- id: event.id,
7483
- kind: event.kind,
7484
- personId: event.personId ?? null,
7485
- aliasId: event.aliasId ?? null,
7486
- actorId: event.actorId,
7487
- mutationId: event.mutationId,
7488
- createdAt: event.createdAt
7489
- }))
7628
+ ...projection
7490
7629
  });
7491
7630
  };
7492
7631
 
@@ -7679,12 +7818,12 @@ function projectNetworkCommercialEvents(input, sources) {
7679
7818
  const prior = latest.get(key);
7680
7819
  if (!prior || order(prior, fact) <= 0) latest.set(key, row);
7681
7820
  }
7682
- const rows6 = [...latest.values()];
7683
- const customers = rows6.filter((row) => row.objectKind === "customer");
7684
- const subscriptions = rows6.filter((row) => row.objectKind === "subscription");
7685
- const invoices = rows6.filter((row) => row.objectKind === "invoice");
7686
- const refunds = rows6.filter((row) => row.objectKind === "refund" && row.eventType.startsWith("refund."));
7687
- for (const charge of rows6.filter((row) => row.eventType === "charge.refunded")) {
7821
+ const rows5 = [...latest.values()];
7822
+ const customers = rows5.filter((row) => row.objectKind === "customer");
7823
+ const subscriptions = rows5.filter((row) => row.objectKind === "subscription");
7824
+ const invoices = rows5.filter((row) => row.objectKind === "invoice");
7825
+ const refunds = rows5.filter((row) => row.objectKind === "refund" && row.eventType.startsWith("refund."));
7826
+ for (const charge of rows5.filter((row) => row.eventType === "charge.refunded")) {
7688
7827
  if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
7689
7828
  exceptions.push({ eventId: charge.eventId, kind: "charge_refund_without_refund_object", sourceKey: charge.sourceKey, detail: { eventType: charge.eventType, objectId: charge.objectId } });
7690
7829
  }
@@ -7713,7 +7852,7 @@ var materializedNamespaces = [
7713
7852
  "networkCommercialInvoices",
7714
7853
  "networkCommercialRefunds"
7715
7854
  ];
7716
- async function rows5(db, ns) {
7855
+ async function rows4(db, ns) {
7717
7856
  const all = [];
7718
7857
  const pageSize = 500;
7719
7858
  for (let offset = 0; ; offset += pageSize) {
@@ -7724,9 +7863,9 @@ async function rows5(db, ns) {
7724
7863
  }
7725
7864
  }
7726
7865
  async function loadNetworkCommercialProjection(db, sources) {
7727
- const stored = await rows5(db, "networkCommercialEvents");
7866
+ const stored = await rows4(db, "networkCommercialEvents");
7728
7867
  const projection = projectNetworkCommercialEvents(stored.map((row) => row.fact), sources);
7729
- const persisted = await rows5(db, "networkCommercialExceptions");
7868
+ const persisted = await rows4(db, "networkCommercialExceptions");
7730
7869
  for (const row of persisted) {
7731
7870
  if (row.status !== "active" || row.kind !== "collision") continue;
7732
7871
  if (projection.exceptions.some((item) => item.eventId === row.eventId && item.kind === "collision")) continue;
@@ -7764,8 +7903,8 @@ function projectionOps(projection, now) {
7764
7903
  async function rebuildNetworkCommercialProjection(db, sources, mutationId, now = Date.now()) {
7765
7904
  const projection = await loadNetworkCommercialProjection(db, sources);
7766
7905
  const [existing, ...materialized] = await Promise.all([
7767
- rows5(db, "networkCommercialExceptions"),
7768
- ...materializedNamespaces.map((ns) => rows5(db, ns))
7906
+ rows4(db, "networkCommercialExceptions"),
7907
+ ...materializedNamespaces.map((ns) => rows4(db, ns))
7769
7908
  ]);
7770
7909
  const activeIds = new Set(projection.exceptions.map((item) => exceptionId(item.eventId, item.kind)));
7771
7910
  const projectedIds = new Set(projectionOps(projection, now).map((op) => String(op.id)));
@@ -7874,11 +8013,11 @@ async function listStripeObjects(client, path, params = {}, maxPages = 20) {
7874
8013
  if (!Array.isArray(page.data) || typeof page.has_more !== "boolean") {
7875
8014
  throw new Error("Stripe returned an invalid commercial list response");
7876
8015
  }
7877
- const rows6 = page.data.map(asRaw).filter((row) => row !== null);
7878
- if (rows6.length !== page.data.length) throw new Error("Stripe returned a malformed commercial object");
7879
- objects.push(...rows6);
8016
+ const rows5 = page.data.map(asRaw).filter((row) => row !== null);
8017
+ if (rows5.length !== page.data.length) throw new Error("Stripe returned a malformed commercial object");
8018
+ objects.push(...rows5);
7880
8019
  if (!page.has_more) return { objects, complete: true };
7881
- const cursor = rows6.at(-1)?.id;
8020
+ const cursor = rows5.at(-1)?.id;
7882
8021
  if (typeof cursor !== "string" || !cursor) throw new Error("Stripe pagination did not provide a cursor");
7883
8022
  startingAfter = cursor;
7884
8023
  }
@@ -8130,14 +8269,14 @@ var PAGE2 = 500;
8130
8269
  var MAX_SCAN = 5e3;
8131
8270
  var MAX_RECORDS2 = 500;
8132
8271
  async function localCommercialRows(db) {
8133
- const rows6 = [];
8272
+ const rows5 = [];
8134
8273
  for (let offset = 0; offset < MAX_SCAN; offset += PAGE2) {
8135
8274
  const result = await db.query({ applications: { $: { limit: PAGE2, offset, order: { createdAt: "asc" } } } });
8136
8275
  const page = Array.isArray(result.applications) ? result.applications : [];
8137
- rows6.push(...page.slice(0, MAX_SCAN - rows6.length));
8138
- if (page.length < PAGE2) return { rows: rows6, truncated: false };
8276
+ rows5.push(...page.slice(0, MAX_SCAN - rows5.length));
8277
+ if (page.length < PAGE2) return { rows: rows5, truncated: false };
8139
8278
  }
8140
- return { rows: rows6, truncated: true };
8279
+ return { rows: rows5, truncated: true };
8141
8280
  }
8142
8281
  var handleNetworkCommercialParity = async (req, url, env, ctx) => {
8143
8282
  if (req.method !== "GET" || url.pathname !== PARITY_PATH) return null;
@@ -8696,17 +8835,17 @@ async function builderLibraryDigest(document) {
8696
8835
  var control2 = (row) => row?.document && typeof row.document === "object" ? row : null;
8697
8836
  var local2 = (row) => row?.document && typeof row.document === "object" ? row : null;
8698
8837
  async function loadActiveBuilderLibraryControls(db, targetId, environment) {
8699
- const rows6 = (await db.query({ builderLibraryControls: { $: { where: { targetId, environment, active: true }, limit: 500 } } })).builderLibraryControls;
8700
- return Array.isArray(rows6) ? rows6.map(control2).filter(Boolean) : [];
8838
+ const rows5 = (await db.query({ builderLibraryControls: { $: { where: { targetId, environment, active: true }, limit: 500 } } })).builderLibraryControls;
8839
+ return Array.isArray(rows5) ? rows5.map(control2).filter(Boolean) : [];
8701
8840
  }
8702
8841
  async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environment) {
8703
- const rows6 = (await db.query({ builderLibraryControls: { $: { where: { sourceId, targetId, environment }, order: { revision: "desc" }, limit: 20 } } })).builderLibraryControls;
8704
- return control2(Array.isArray(rows6) ? [...rows6].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
8842
+ const rows5 = (await db.query({ builderLibraryControls: { $: { where: { sourceId, targetId, environment }, order: { revision: "desc" }, limit: 20 } } })).builderLibraryControls;
8843
+ return control2(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
8705
8844
  }
8706
8845
  async function loadLocalBuilderLibrary(db, appId, environment) {
8707
8846
  const id = `builder-library-local:${environment}`;
8708
- const rows6 = (await db.query({ builderLibraryLocal: { $: { where: { id }, limit: 1 } } })).builderLibraryLocal;
8709
- return local2(Array.isArray(rows6) ? rows6[0] : void 0);
8847
+ const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id }, limit: 1 } } })).builderLibraryLocal;
8848
+ return local2(Array.isArray(rows5) ? rows5[0] : void 0);
8710
8849
  }
8711
8850
  async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
8712
8851
  const { document, digest: digest2 } = envelope;