@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.
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +321 -173
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.js +321 -173
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -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,
|
|
379
|
-
const stored =
|
|
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
|
|
425
|
+
let rows5;
|
|
426
426
|
try {
|
|
427
|
-
|
|
427
|
+
rows5 = (await db.query({
|
|
428
428
|
tiers: { $: { where: { groupId }, order: { sortOrder: "asc" }, limit: 100 } }
|
|
429
429
|
})).tiers;
|
|
430
430
|
} catch {
|
|
431
|
-
|
|
431
|
+
rows5 = void 0;
|
|
432
432
|
}
|
|
433
|
-
const list = Array.isArray(
|
|
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
|
|
1488
|
-
return Array.isArray(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
2884
|
-
return
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
2909
|
-
return local(Array.isArray(
|
|
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
|
|
2964
|
+
let rows5 = (await db.query({ applications: { $: {
|
|
2965
2965
|
where: { emailNormalized: normalizeAdmissionEmail(email) },
|
|
2966
2966
|
limit: 20
|
|
2967
2967
|
} } })).applications;
|
|
2968
|
-
if (!
|
|
2969
|
-
const latest = Array.isArray(
|
|
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
|
|
3696
|
-
return Array.isArray(
|
|
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
|
|
4097
|
-
const paid =
|
|
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(
|
|
4206
|
-
return
|
|
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
|
|
4499
|
-
return Array.isArray(
|
|
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
|
|
4570
|
-
return Array.isArray(
|
|
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(
|
|
5037
|
-
return
|
|
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
|
|
5060
|
-
if (Number.isFinite(from))
|
|
5061
|
-
if (Number.isFinite(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(
|
|
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 =
|
|
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
|
|
5167
|
+
const rows5 = [...people.values()].sort(
|
|
5168
5168
|
(x, y) => (y.application?.createdAt ?? -1) - (x.application?.createdAt ?? -1)
|
|
5169
5169
|
);
|
|
5170
|
-
return json({ people:
|
|
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;
|
|
@@ -7013,6 +7013,9 @@ async function networkSourceKey(sourceSystem, sourceRecordId) {
|
|
|
7013
7013
|
async function networkChapterLinkId(personId, chapterId, chapterPersonId) {
|
|
7014
7014
|
return `chapter_person_${await sha256(`${personId}\0${chapterId}\0${chapterPersonId}`)}`;
|
|
7015
7015
|
}
|
|
7016
|
+
async function networkChapterPersonSourceKey(chapterId, chapterPersonId) {
|
|
7017
|
+
return `chapter_source_${await sha256(`${chapterId}\0${chapterPersonId}`)}`;
|
|
7018
|
+
}
|
|
7016
7019
|
function projectNetworkContactPolicy(policy, links) {
|
|
7017
7020
|
return links.map((link) => ({ ...link, contactPolicy: { ...policy } }));
|
|
7018
7021
|
}
|
|
@@ -7030,7 +7033,7 @@ function canonicalNetworkPersonId(personId, persons) {
|
|
|
7030
7033
|
}
|
|
7031
7034
|
}
|
|
7032
7035
|
|
|
7033
|
-
// src/network-person-store.ts
|
|
7036
|
+
// src/network-person-store-state.ts
|
|
7034
7037
|
var NetworkPersonRegistryError = class extends Error {
|
|
7035
7038
|
constructor(code, message) {
|
|
7036
7039
|
super(message);
|
|
@@ -7038,29 +7041,112 @@ var NetworkPersonRegistryError = class extends Error {
|
|
|
7038
7041
|
}
|
|
7039
7042
|
code;
|
|
7040
7043
|
};
|
|
7041
|
-
var
|
|
7042
|
-
async function loadNetworkPersonRegistry(db) {
|
|
7043
|
-
const result = await db.query({
|
|
7044
|
-
networkPersons: { $: { order: { updatedAt: "desc" }, limit: 1e3 } },
|
|
7045
|
-
networkIdentityClaims: { $: { limit: 1e3 } },
|
|
7046
|
-
networkPersonAliases: { $: { order: { updatedAt: "desc" }, limit: 1e3 } },
|
|
7047
|
-
networkChapterPersonLinks: { $: { limit: 1e3 } },
|
|
7048
|
-
networkPersonEvents: { $: { order: { createdAt: "desc" }, limit: 1e3 } }
|
|
7049
|
-
});
|
|
7050
|
-
return {
|
|
7051
|
-
persons: rows4(result.networkPersons),
|
|
7052
|
-
claims: rows4(result.networkIdentityClaims),
|
|
7053
|
-
aliases: rows4(result.networkPersonAliases),
|
|
7054
|
-
links: rows4(result.networkChapterPersonLinks),
|
|
7055
|
-
events: rows4(result.networkPersonEvents)
|
|
7056
|
-
};
|
|
7057
|
-
}
|
|
7058
|
-
var eventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
|
|
7044
|
+
var networkPersonEventOp = (kind, mutationId, actor, now, detail, ids = {}) => ({
|
|
7059
7045
|
t: "update",
|
|
7060
7046
|
ns: "networkPersonEvents",
|
|
7061
7047
|
id: `network_person_event:${mutationId}`,
|
|
7062
7048
|
attrs: { id: `network_person_event:${mutationId}`, kind, actorId: actor.id, ...ids, mutationId, detail, createdAt: now }
|
|
7063
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
|
|
7064
7150
|
var defaultPolicy = (now, consent) => ({
|
|
7065
7151
|
marketingSuppressed: consent.marketing !== "granted",
|
|
7066
7152
|
discretionarySuppressed: consent.discretionary !== "granted",
|
|
@@ -7070,7 +7156,8 @@ var isGuardConflict = (error) => error instanceof Error && error.code === "trans
|
|
|
7070
7156
|
async function linkOps(personId, links, now) {
|
|
7071
7157
|
return Promise.all(links.map(async (link) => {
|
|
7072
7158
|
const id = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
|
|
7073
|
-
|
|
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 } };
|
|
7074
7161
|
}));
|
|
7075
7162
|
}
|
|
7076
7163
|
async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
|
|
@@ -7092,7 +7179,7 @@ async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId
|
|
|
7092
7179
|
};
|
|
7093
7180
|
await db.transact([
|
|
7094
7181
|
{ t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...alias } },
|
|
7095
|
-
|
|
7182
|
+
networkPersonEventOp("import_queued_ambiguous", mutationId, actor, now, { candidateCount: candidates.length, sourceSystem: input.sourceSystem }, { aliasId })
|
|
7096
7183
|
], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: false }] });
|
|
7097
7184
|
return { status: "ambiguous", alias };
|
|
7098
7185
|
}
|
|
@@ -7128,7 +7215,7 @@ async function importNetworkPerson(db, input, mutationId, actor, now = Date.now(
|
|
|
7128
7215
|
{ t: "update", ns: "networkPersonAliases", id: aliasId, attrs: { ...alias } },
|
|
7129
7216
|
...claimOps,
|
|
7130
7217
|
...await linkOps(personId, input.chapterLinks ?? [], now),
|
|
7131
|
-
|
|
7218
|
+
networkPersonEventOp("person_imported", mutationId, actor, now, { sourceSystem: input.sourceSystem, identityCount: claims.length }, { personId, aliasId })
|
|
7132
7219
|
];
|
|
7133
7220
|
try {
|
|
7134
7221
|
await db.transact(ops, { mutationId, guards: [
|
|
@@ -7150,12 +7237,21 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
|
|
|
7150
7237
|
const state = await loadNetworkPersonRegistry(db);
|
|
7151
7238
|
const alias = state.aliases.find((row) => row.id === input.aliasId);
|
|
7152
7239
|
if (!alias) throw new NetworkPersonRegistryError("alias_not_found", "source alias not found");
|
|
7153
|
-
|
|
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
|
+
}
|
|
7154
7250
|
if (input.decision === "exclude") {
|
|
7155
7251
|
const next2 = { ...alias, status: "excluded", revision: alias.revision + 1, updatedAt: now };
|
|
7156
7252
|
await db.transact([
|
|
7157
7253
|
{ t: "update", ns: "networkPersonAliases", id: alias.id, attrs: { ...next2 } },
|
|
7158
|
-
|
|
7254
|
+
networkPersonEventOp("alias_excluded", mutationId, actor, now, { sourceSystem: alias.sourceSystem }, { aliasId: alias.id })
|
|
7159
7255
|
], { mutationId, guards: [{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } }] });
|
|
7160
7256
|
return next2;
|
|
7161
7257
|
}
|
|
@@ -7171,7 +7267,7 @@ async function reviewNetworkPersonAlias(db, input, mutationId, actor, now = Date
|
|
|
7171
7267
|
{ t: "retract", ns: "networkPersonAliases", id: alias.id, attrs: ["candidatePersonIds"] },
|
|
7172
7268
|
...missing.map((claim) => ({ t: "update", ns: "networkIdentityClaims", id: claim.id, attrs: { ...claim, personId: canonicalId, createdAt: now } })),
|
|
7173
7269
|
...await linkOps(canonicalId, alias.chapterLinks, now),
|
|
7174
|
-
|
|
7270
|
+
networkPersonEventOp("alias_linked", mutationId, actor, now, { identityCount: alias.identityClaims.length }, { personId: canonicalId, aliasId: alias.id })
|
|
7175
7271
|
], { mutationId, guards: [
|
|
7176
7272
|
{ ns: "networkPersonAliases", id: alias.id, exists: true, equals: { revision: alias.revision, status: "ambiguous" } },
|
|
7177
7273
|
...missing.map((claim) => ({ ns: "networkIdentityClaims", id: claim.id, exists: false }))
|
|
@@ -7201,7 +7297,7 @@ async function mergeNetworkPersons(db, input, mutationId, actor, now = Date.now(
|
|
|
7201
7297
|
await db.transact([
|
|
7202
7298
|
{ t: "update", ns: "networkPersons", id: source.id, attrs: { ...sourceNext } },
|
|
7203
7299
|
{ t: "update", ns: "networkPersons", id: target.id, attrs: { ...targetNext } },
|
|
7204
|
-
|
|
7300
|
+
networkPersonEventOp("persons_merged", mutationId, actor, now, { targetPersonId: target.id }, { personId: source.id })
|
|
7205
7301
|
], { mutationId, guards: [
|
|
7206
7302
|
{ ns: "networkPersons", id: source.id, exists: true, equals: { revision: source.revision, state: "active" } },
|
|
7207
7303
|
{ ns: "networkPersons", id: target.id, exists: true, equals: { revision: target.revision, state: "active" } }
|
|
@@ -7217,7 +7313,7 @@ async function unmergeNetworkPerson(db, input, mutationId, actor, now = Date.now
|
|
|
7217
7313
|
await db.transact([
|
|
7218
7314
|
{ t: "update", ns: "networkPersons", id: person.id, attrs: { state: "active", revision: next.revision, updatedAt: now } },
|
|
7219
7315
|
{ t: "retract", ns: "networkPersons", id: person.id, attrs: ["mergedIntoId"] },
|
|
7220
|
-
|
|
7316
|
+
networkPersonEventOp("person_unmerged", mutationId, actor, now, {}, { personId: person.id })
|
|
7221
7317
|
], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "merged", mergedIntoId: person.mergedIntoId } }] });
|
|
7222
7318
|
return next;
|
|
7223
7319
|
}
|
|
@@ -7231,63 +7327,30 @@ async function suppressNetworkPerson(db, input, mutationId, actor, now = Date.no
|
|
|
7231
7327
|
const next = { ...person, contactPolicy: policy, revision: person.revision + 1, updatedAt: now };
|
|
7232
7328
|
await db.transact([
|
|
7233
7329
|
{ t: "update", ns: "networkPersons", id: person.id, attrs: { ...next } },
|
|
7234
|
-
|
|
7330
|
+
networkPersonEventOp("contact_suppression_changed", mutationId, actor, now, { marketingSuppressed: policy.marketingSuppressed, discretionarySuppressed: policy.discretionarySuppressed }, { personId: person.id })
|
|
7235
7331
|
], { mutationId, guards: [{ ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision } }] });
|
|
7236
7332
|
return next;
|
|
7237
7333
|
}
|
|
7238
7334
|
|
|
7239
|
-
// src/network-person-
|
|
7240
|
-
async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, now = Date.now()) {
|
|
7241
|
-
const reason = input.reason.trim();
|
|
7242
|
-
if (!reason || reason.length > 500) {
|
|
7243
|
-
throw new NetworkPersonRegistryError("invalid_reveal", "a bounded reveal reason is required");
|
|
7244
|
-
}
|
|
7245
|
-
const state = await loadNetworkPersonRegistry(db);
|
|
7246
|
-
const target = input.kind === "person" ? state.persons.find((row) => row.id === input.id) : state.aliases.find((row) => row.id === input.id);
|
|
7247
|
-
if (!target) throw new NetworkPersonRegistryError("reveal_not_found", "registry record not found");
|
|
7248
|
-
const ids = input.kind === "person" ? { personId: input.id } : { aliasId: input.id };
|
|
7249
|
-
const event = {
|
|
7250
|
-
t: "update",
|
|
7251
|
-
ns: "networkPersonEvents",
|
|
7252
|
-
id: `network_person_event:${mutationId}`,
|
|
7253
|
-
attrs: {
|
|
7254
|
-
id: `network_person_event:${mutationId}`,
|
|
7255
|
-
kind: "pii_revealed",
|
|
7256
|
-
actorId: actor.id,
|
|
7257
|
-
...ids,
|
|
7258
|
-
mutationId,
|
|
7259
|
-
detail: { targetKind: input.kind, reason },
|
|
7260
|
-
createdAt: now
|
|
7261
|
-
}
|
|
7262
|
-
};
|
|
7263
|
-
await db.transact([event], {
|
|
7264
|
-
mutationId,
|
|
7265
|
-
guards: [{ ns: "networkPersonEvents", id: `network_person_event:${mutationId}`, exists: false }]
|
|
7266
|
-
});
|
|
7267
|
-
return input.kind === "person" ? { kind: "person", person: target, identities: state.claims.filter((claim) => claim.personId === input.id) } : { kind: "alias", alias: target };
|
|
7268
|
-
}
|
|
7269
|
-
|
|
7270
|
-
// src/worker-routes-network-person-registry.ts
|
|
7271
|
-
var PATH2 = "/api/admin/network-person-registry";
|
|
7272
|
-
var MAX_BODY2 = 32e3;
|
|
7273
|
-
var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
|
|
7335
|
+
// src/network-person-route-projection.ts
|
|
7274
7336
|
function redactedName(displayName) {
|
|
7275
7337
|
return displayName.split(/\s+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}\u2026`).join(" ");
|
|
7276
7338
|
}
|
|
7277
|
-
var
|
|
7339
|
+
var redactedNetworkPersonLink = async (link) => ({
|
|
7278
7340
|
id: link.id,
|
|
7279
7341
|
personId: link.personId,
|
|
7280
7342
|
chapterId: link.chapterId,
|
|
7343
|
+
sourceKey: await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId),
|
|
7281
7344
|
state: link.state,
|
|
7282
7345
|
createdAt: link.createdAt
|
|
7283
7346
|
});
|
|
7284
|
-
var
|
|
7347
|
+
var safeNetworkContactPolicy = (policy) => ({
|
|
7285
7348
|
marketingSuppressed: policy.marketingSuppressed,
|
|
7286
7349
|
discretionarySuppressed: policy.discretionarySuppressed,
|
|
7287
7350
|
hasReason: Boolean(policy.reason),
|
|
7288
7351
|
updatedAt: policy.updatedAt
|
|
7289
7352
|
});
|
|
7290
|
-
var
|
|
7353
|
+
var safeNetworkPersonAlias = async (alias) => ({
|
|
7291
7354
|
id: alias.id,
|
|
7292
7355
|
sourceSystem: alias.sourceSystem,
|
|
7293
7356
|
displayNameHint: redactedName(alias.displayName),
|
|
@@ -7302,17 +7365,91 @@ var safeAlias = (alias) => ({
|
|
|
7302
7365
|
},
|
|
7303
7366
|
identityClaims: alias.identityClaims.map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
|
|
7304
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
|
+
}))),
|
|
7305
7372
|
revision: alias.revision,
|
|
7306
7373
|
updatedAt: alias.updatedAt
|
|
7307
7374
|
});
|
|
7308
|
-
var
|
|
7375
|
+
var safeNetworkPerson = (person, claims = []) => ({
|
|
7309
7376
|
id: person.id,
|
|
7377
|
+
displayName: person.displayName,
|
|
7310
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)),
|
|
7311
7380
|
state: person.state,
|
|
7312
7381
|
mergedIntoId: person.mergedIntoId ?? null,
|
|
7313
7382
|
revision: person.revision,
|
|
7314
|
-
contactPolicy:
|
|
7383
|
+
contactPolicy: safeNetworkContactPolicy(person.contactPolicy)
|
|
7315
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}$/;
|
|
7316
7453
|
var positiveRevision = (value) => {
|
|
7317
7454
|
const parsed = Number(value);
|
|
7318
7455
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
@@ -7368,14 +7505,14 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
7368
7505
|
return json({
|
|
7369
7506
|
result: {
|
|
7370
7507
|
status: result.status,
|
|
7371
|
-
alias:
|
|
7372
|
-
..."person" in result && result.person ? { person:
|
|
7508
|
+
alias: await safeNetworkPersonAlias(result.alias),
|
|
7509
|
+
..."person" in result && result.person ? { person: safeNetworkPerson(result.person, result.alias.identityClaims) } : {},
|
|
7373
7510
|
..."replayed" in result ? { replayed: result.replayed } : {}
|
|
7374
7511
|
}
|
|
7375
7512
|
}, 201);
|
|
7376
7513
|
}
|
|
7377
7514
|
if (command === "review") {
|
|
7378
|
-
if (input.decision !== "link" && input.decision !== "exclude") return json({ error: "review decision must be link or
|
|
7515
|
+
if (input.decision !== "link" && input.decision !== "exclude" && input.decision !== "reopen") return json({ error: "review decision must be link, exclude, or reopen" }, 400);
|
|
7379
7516
|
const expectedRevision = positiveRevision(input.expectedRevision);
|
|
7380
7517
|
if (!expectedRevision) return json({ error: "expectedRevision is required" }, 400);
|
|
7381
7518
|
if (input.decision === "link" && typeof input.personId !== "string") return json({ error: "personId is required when linking" }, 400);
|
|
@@ -7385,7 +7522,42 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
7385
7522
|
decision: input.decision,
|
|
7386
7523
|
...typeof input.personId === "string" ? { personId: input.personId } : {}
|
|
7387
7524
|
}, mutationId, actionActor);
|
|
7388
|
-
return json({ 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 } });
|
|
7389
7561
|
}
|
|
7390
7562
|
if (command === "merge") {
|
|
7391
7563
|
const expectedRevision = positiveRevision(input.expectedRevision);
|
|
@@ -7397,7 +7569,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
7397
7569
|
expectedRevision,
|
|
7398
7570
|
expectedTargetRevision
|
|
7399
7571
|
}, mutationId, actionActor);
|
|
7400
|
-
return json({ result: { source:
|
|
7572
|
+
return json({ result: { source: safeNetworkPerson(result.source), target: safeNetworkPerson(result.target) } });
|
|
7401
7573
|
}
|
|
7402
7574
|
if (command === "unmerge") {
|
|
7403
7575
|
const expectedRevision = positiveRevision(input.expectedRevision);
|
|
@@ -7406,7 +7578,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
7406
7578
|
personId: typeof input.personId === "string" ? input.personId : "",
|
|
7407
7579
|
expectedRevision
|
|
7408
7580
|
}, mutationId, actionActor);
|
|
7409
|
-
return json({ result:
|
|
7581
|
+
return json({ result: safeNetworkPerson(result) });
|
|
7410
7582
|
}
|
|
7411
7583
|
if (command === "suppression") {
|
|
7412
7584
|
const expectedRevision = positiveRevision(input.expectedRevision);
|
|
@@ -7422,7 +7594,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
7422
7594
|
discretionarySuppressed: input.discretionarySuppressed,
|
|
7423
7595
|
...reason ? { reason } : {}
|
|
7424
7596
|
}, mutationId, actionActor);
|
|
7425
|
-
return json({ result:
|
|
7597
|
+
return json({ result: safeNetworkPerson(result) });
|
|
7426
7598
|
}
|
|
7427
7599
|
if (command === "reveal") {
|
|
7428
7600
|
if (input.kind !== "person" && input.kind !== "alias") return json({ error: "reveal kind must be person or alias" }, 400);
|
|
@@ -7441,43 +7613,19 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
7441
7613
|
}
|
|
7442
7614
|
}
|
|
7443
7615
|
const state = await loadNetworkPersonRegistry(db);
|
|
7444
|
-
const
|
|
7445
|
-
const links = state.links.filter((link) => link.personId === person.id);
|
|
7446
|
-
const canonicalId = canonicalNetworkPersonId(person.id, state.persons);
|
|
7447
|
-
const canonical2 = state.persons.find((candidate) => candidate.id === canonicalId) ?? person;
|
|
7448
|
-
return {
|
|
7449
|
-
id: person.id,
|
|
7450
|
-
displayNameHint: redactedName(person.displayName),
|
|
7451
|
-
state: person.state,
|
|
7452
|
-
canonicalPersonId: canonicalId,
|
|
7453
|
-
mergedIntoId: person.mergedIntoId ?? null,
|
|
7454
|
-
revision: person.revision,
|
|
7455
|
-
contactPolicy: safePolicy(person.contactPolicy),
|
|
7456
|
-
identityClaims: state.claims.filter((claim) => claim.personId === person.id).map(({ id, kind, hint, verifiedAt }) => ({ id, kind, hint: hint ?? null, verifiedAt })),
|
|
7457
|
-
chapterLinks: links.map(redactedLink),
|
|
7458
|
-
chapterProjections: projectNetworkContactPolicy(canonical2.contactPolicy, links.map(redactedLink)).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safePolicy(contactPolicy) }))
|
|
7459
|
-
};
|
|
7460
|
-
});
|
|
7461
|
-
const aliases = state.aliases.map(safeAlias);
|
|
7616
|
+
const projection = await projectNetworkPersonRegistryState(state);
|
|
7462
7617
|
return json({
|
|
7463
7618
|
version: 1,
|
|
7464
7619
|
mode: "shadow",
|
|
7465
7620
|
writable: true,
|
|
7466
7621
|
authoritativeForChapterOperation: false,
|
|
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,
|
|
7467
7627
|
configuredChapterIds: ctx.chapter.networkPersonRegistry.chapterIds,
|
|
7468
|
-
|
|
7469
|
-
aliases,
|
|
7470
|
-
reviewQueue: aliases.filter((alias) => alias.status === "ambiguous"),
|
|
7471
|
-
excluded: aliases.filter((alias) => alias.status === "excluded"),
|
|
7472
|
-
recentEvents: state.events.slice(0, 100).map((event) => ({
|
|
7473
|
-
id: event.id,
|
|
7474
|
-
kind: event.kind,
|
|
7475
|
-
personId: event.personId ?? null,
|
|
7476
|
-
aliasId: event.aliasId ?? null,
|
|
7477
|
-
actorId: event.actorId,
|
|
7478
|
-
mutationId: event.mutationId,
|
|
7479
|
-
createdAt: event.createdAt
|
|
7480
|
-
}))
|
|
7628
|
+
...projection
|
|
7481
7629
|
});
|
|
7482
7630
|
};
|
|
7483
7631
|
|
|
@@ -7670,12 +7818,12 @@ function projectNetworkCommercialEvents(input, sources) {
|
|
|
7670
7818
|
const prior = latest.get(key);
|
|
7671
7819
|
if (!prior || order(prior, fact) <= 0) latest.set(key, row);
|
|
7672
7820
|
}
|
|
7673
|
-
const
|
|
7674
|
-
const customers =
|
|
7675
|
-
const subscriptions =
|
|
7676
|
-
const invoices =
|
|
7677
|
-
const refunds =
|
|
7678
|
-
for (const charge of
|
|
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")) {
|
|
7679
7827
|
if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
|
|
7680
7828
|
exceptions.push({ eventId: charge.eventId, kind: "charge_refund_without_refund_object", sourceKey: charge.sourceKey, detail: { eventType: charge.eventType, objectId: charge.objectId } });
|
|
7681
7829
|
}
|
|
@@ -7704,7 +7852,7 @@ var materializedNamespaces = [
|
|
|
7704
7852
|
"networkCommercialInvoices",
|
|
7705
7853
|
"networkCommercialRefunds"
|
|
7706
7854
|
];
|
|
7707
|
-
async function
|
|
7855
|
+
async function rows4(db, ns) {
|
|
7708
7856
|
const all = [];
|
|
7709
7857
|
const pageSize = 500;
|
|
7710
7858
|
for (let offset = 0; ; offset += pageSize) {
|
|
@@ -7715,9 +7863,9 @@ async function rows5(db, ns) {
|
|
|
7715
7863
|
}
|
|
7716
7864
|
}
|
|
7717
7865
|
async function loadNetworkCommercialProjection(db, sources) {
|
|
7718
|
-
const stored = await
|
|
7866
|
+
const stored = await rows4(db, "networkCommercialEvents");
|
|
7719
7867
|
const projection = projectNetworkCommercialEvents(stored.map((row) => row.fact), sources);
|
|
7720
|
-
const persisted = await
|
|
7868
|
+
const persisted = await rows4(db, "networkCommercialExceptions");
|
|
7721
7869
|
for (const row of persisted) {
|
|
7722
7870
|
if (row.status !== "active" || row.kind !== "collision") continue;
|
|
7723
7871
|
if (projection.exceptions.some((item) => item.eventId === row.eventId && item.kind === "collision")) continue;
|
|
@@ -7755,8 +7903,8 @@ function projectionOps(projection, now) {
|
|
|
7755
7903
|
async function rebuildNetworkCommercialProjection(db, sources, mutationId, now = Date.now()) {
|
|
7756
7904
|
const projection = await loadNetworkCommercialProjection(db, sources);
|
|
7757
7905
|
const [existing, ...materialized] = await Promise.all([
|
|
7758
|
-
|
|
7759
|
-
...materializedNamespaces.map((ns) =>
|
|
7906
|
+
rows4(db, "networkCommercialExceptions"),
|
|
7907
|
+
...materializedNamespaces.map((ns) => rows4(db, ns))
|
|
7760
7908
|
]);
|
|
7761
7909
|
const activeIds = new Set(projection.exceptions.map((item) => exceptionId(item.eventId, item.kind)));
|
|
7762
7910
|
const projectedIds = new Set(projectionOps(projection, now).map((op) => String(op.id)));
|
|
@@ -7865,11 +8013,11 @@ async function listStripeObjects(client, path, params = {}, maxPages = 20) {
|
|
|
7865
8013
|
if (!Array.isArray(page.data) || typeof page.has_more !== "boolean") {
|
|
7866
8014
|
throw new Error("Stripe returned an invalid commercial list response");
|
|
7867
8015
|
}
|
|
7868
|
-
const
|
|
7869
|
-
if (
|
|
7870
|
-
objects.push(...
|
|
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);
|
|
7871
8019
|
if (!page.has_more) return { objects, complete: true };
|
|
7872
|
-
const cursor =
|
|
8020
|
+
const cursor = rows5.at(-1)?.id;
|
|
7873
8021
|
if (typeof cursor !== "string" || !cursor) throw new Error("Stripe pagination did not provide a cursor");
|
|
7874
8022
|
startingAfter = cursor;
|
|
7875
8023
|
}
|
|
@@ -8121,14 +8269,14 @@ var PAGE2 = 500;
|
|
|
8121
8269
|
var MAX_SCAN = 5e3;
|
|
8122
8270
|
var MAX_RECORDS2 = 500;
|
|
8123
8271
|
async function localCommercialRows(db) {
|
|
8124
|
-
const
|
|
8272
|
+
const rows5 = [];
|
|
8125
8273
|
for (let offset = 0; offset < MAX_SCAN; offset += PAGE2) {
|
|
8126
8274
|
const result = await db.query({ applications: { $: { limit: PAGE2, offset, order: { createdAt: "asc" } } } });
|
|
8127
8275
|
const page = Array.isArray(result.applications) ? result.applications : [];
|
|
8128
|
-
|
|
8129
|
-
if (page.length < PAGE2) return { rows:
|
|
8276
|
+
rows5.push(...page.slice(0, MAX_SCAN - rows5.length));
|
|
8277
|
+
if (page.length < PAGE2) return { rows: rows5, truncated: false };
|
|
8130
8278
|
}
|
|
8131
|
-
return { rows:
|
|
8279
|
+
return { rows: rows5, truncated: true };
|
|
8132
8280
|
}
|
|
8133
8281
|
var handleNetworkCommercialParity = async (req, url, env, ctx) => {
|
|
8134
8282
|
if (req.method !== "GET" || url.pathname !== PARITY_PATH) return null;
|
|
@@ -8687,17 +8835,17 @@ async function builderLibraryDigest(document) {
|
|
|
8687
8835
|
var control2 = (row) => row?.document && typeof row.document === "object" ? row : null;
|
|
8688
8836
|
var local2 = (row) => row?.document && typeof row.document === "object" ? row : null;
|
|
8689
8837
|
async function loadActiveBuilderLibraryControls(db, targetId, environment) {
|
|
8690
|
-
const
|
|
8691
|
-
return Array.isArray(
|
|
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) : [];
|
|
8692
8840
|
}
|
|
8693
8841
|
async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environment) {
|
|
8694
|
-
const
|
|
8695
|
-
return control2(Array.isArray(
|
|
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);
|
|
8696
8844
|
}
|
|
8697
8845
|
async function loadLocalBuilderLibrary(db, appId, environment) {
|
|
8698
8846
|
const id = `builder-library-local:${environment}`;
|
|
8699
|
-
const
|
|
8700
|
-
return local2(Array.isArray(
|
|
8847
|
+
const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id }, limit: 1 } } })).builderLibraryLocal;
|
|
8848
|
+
return local2(Array.isArray(rows5) ? rows5[0] : void 0);
|
|
8701
8849
|
}
|
|
8702
8850
|
async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
|
|
8703
8851
|
const { document, digest: digest2 } = envelope;
|