@odla-ai/chapter 0.42.1 → 0.42.3

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.
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/worker.ts
21
21
  var worker_exports = {};
22
22
  __export(worker_exports, {
23
+ AdmissionGrantDeliveryError: () => AdmissionGrantDeliveryError,
23
24
  chapterWorker: () => chapterWorker,
24
25
  createWorkerContext: () => createWorkerContext,
25
26
  deliverAdmissionGrants: () => deliverAdmissionGrants,
@@ -27,6 +28,7 @@ __export(worker_exports, {
27
28
  deliverMemberContent: () => deliverMemberContent,
28
29
  deliverSignupControl: () => deliverSignupControl,
29
30
  renderActiveSignupControlHtml: () => renderActiveSignupControlHtml,
31
+ retryLeaderAdmissionDeliveries: () => retryLeaderAdmissionDeliveries,
30
32
  withActiveSignupControlTheme: () => withActiveSignupControlTheme
31
33
  });
32
34
  module.exports = __toCommonJS(worker_exports);
@@ -640,6 +642,11 @@ function validateAdmissionGrantDocument(input) {
640
642
  }
641
643
  };
642
644
  }
645
+ async function createAdmissionGrantEnvelope(document) {
646
+ const checked = validateAdmissionGrantDocument(document);
647
+ if (!checked.ok) throw new TypeError(`Invalid admission grant document: ${checked.errors.join("; ")}`);
648
+ return { document: checked.value, digest: await signupControlDigest(checked.value) };
649
+ }
643
650
  var rows = (value) => Array.isArray(value) ? value : [];
644
651
  function asGrant(row) {
645
652
  if (typeof row.id !== "string" || typeof row.emailNormalized !== "string") return null;
@@ -3138,12 +3145,17 @@ var import_db5 = require("@odla-ai/db");
3138
3145
  var reject3 = (reason, status, errors) => json({ ok: false, reason, ...errors ? { errors } : {} }, status);
3139
3146
  var handleAdmissionGrants = async (req, url, env, ctx) => {
3140
3147
  if (req.method !== "POST" || url.pathname !== ADMISSION_GRANTS_PATH) return null;
3141
- const authority = ctx.chapter.signupControl;
3142
- if (!authority) return json({ error: "not found" }, 404);
3148
+ const claimedSender = req.headers.get("x-odla-federation-sender") ?? "";
3149
+ const networkReader = ctx.chapter.network.readers.find((reader) => reader.id === claimedSender && reader.admissionGrants);
3150
+ const signupAuthority = ctx.chapter.signupControl?.sourceId === claimedSender ? ctx.chapter.signupControl : void 0;
3151
+ if (!networkReader && !signupAuthority) return json({ error: "not found" }, 404);
3143
3152
  const db = ctx.makeDb(env);
3144
- const secret = await getVaultSecret(db, authority.secretName);
3153
+ const secret = await getVaultSecret(
3154
+ db,
3155
+ networkReader ? "network_share_secret" : signupAuthority.secretName
3156
+ );
3145
3157
  if (!secret) return json({ error: "unauthorized" }, 401);
3146
- const verified = await (0, import_db5.verifyFederatedRequest)(req, { secret, senders: [authority.sourceId] });
3158
+ const verified = await (0, import_db5.verifyFederatedRequest)(req, { secret, senders: [claimedSender] });
3147
3159
  if (!verified.ok) return json({ error: "unauthorized", reason: verified.reason }, 401);
3148
3160
  let input;
3149
3161
  try {
@@ -3155,11 +3167,11 @@ var handleAdmissionGrants = async (req, url, env, ctx) => {
3155
3167
  const checked = validateAdmissionGrantDocument(input.document);
3156
3168
  if (!checked.ok) return reject3("invalid_document", 400, checked.errors);
3157
3169
  const document = checked.value;
3158
- if (document.source.appId !== authority.sourceId) return reject3("wrong_source", 403);
3170
+ if (document.source.appId !== claimedSender) return reject3("wrong_source", 403);
3159
3171
  if (document.target.chapterId !== ctx.chapter.id) return reject3("wrong_target", 409);
3160
3172
  if (document.target.environment !== env.ODLA_ENV) return reject3("wrong_environment", 409);
3161
3173
  if (!digest || await signupControlDigest(document) !== digest) return reject3("invalid_digest", 400);
3162
- const latest = await loadLatestAdmissionRevision(db, authority.sourceId, ctx.chapter.id, env.ODLA_ENV);
3174
+ const latest = await loadLatestAdmissionRevision(db, claimedSender, ctx.chapter.id, env.ODLA_ENV);
3163
3175
  if (latest && document.revision < latest.revision) return reject3("stale_revision", 409);
3164
3176
  if (latest && document.revision === latest.revision) {
3165
3177
  if (latest.digest !== digest) return reject3("conflicting_revision", 409);
@@ -3284,6 +3296,384 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
3284
3296
  return null;
3285
3297
  };
3286
3298
 
3299
+ // src/worker-routes-network-admission.ts
3300
+ var import_crm6 = require("@odla-ai/crm");
3301
+
3302
+ // src/admission-grants-delivery.ts
3303
+ var import_db6 = require("@odla-ai/db");
3304
+ var AdmissionGrantDeliveryError = class extends Error {
3305
+ constructor(status, reason) {
3306
+ super(`admission grants rejected (${status}): ${reason}`);
3307
+ this.status = status;
3308
+ this.reason = reason;
3309
+ this.name = "AdmissionGrantDeliveryError";
3310
+ }
3311
+ status;
3312
+ reason;
3313
+ };
3314
+ async function deliverAdmissionGrants(options) {
3315
+ const destination = new URL(ADMISSION_GRANTS_PATH, options.targetUrl);
3316
+ const body = JSON.stringify(options.envelope);
3317
+ const signed = await (0, import_db6.signFederatedRequest)({
3318
+ secret: options.secret,
3319
+ sender: options.sender,
3320
+ method: "POST",
3321
+ url: destination,
3322
+ body
3323
+ });
3324
+ const response = await (options.fetch ?? globalThis.fetch)(destination, {
3325
+ method: "POST",
3326
+ headers: { ...signed, "content-type": "application/json" },
3327
+ body
3328
+ });
3329
+ const result = await response.json();
3330
+ if (!response.ok || !("ok" in result) || result.ok !== true) {
3331
+ const failure = result;
3332
+ throw new AdmissionGrantDeliveryError(
3333
+ response.status,
3334
+ failure.reason ?? failure.error ?? "unknown"
3335
+ );
3336
+ }
3337
+ return result;
3338
+ }
3339
+
3340
+ // src/worker-network-fetch.ts
3341
+ var NetworkTargetTransportError = class extends Error {
3342
+ code = "binding_unavailable";
3343
+ binding;
3344
+ constructor(binding) {
3345
+ super(`service binding "${binding}" is unavailable`);
3346
+ this.name = "NetworkTargetTransportError";
3347
+ this.binding = binding;
3348
+ }
3349
+ };
3350
+ function resolveNetworkTargetTransport(env, target) {
3351
+ if (!target.binding) {
3352
+ return {
3353
+ kind: "url",
3354
+ fetch: (destination, init) => globalThis.fetch(destination, init)
3355
+ };
3356
+ }
3357
+ const candidate = env[target.binding];
3358
+ if (typeof candidate !== "object" || candidate === null || !("fetch" in candidate) || typeof candidate.fetch !== "function") {
3359
+ throw new NetworkTargetTransportError(target.binding);
3360
+ }
3361
+ const binding = candidate;
3362
+ return {
3363
+ kind: "binding",
3364
+ binding: target.binding,
3365
+ // Keep the receiver attached: Cloudflare service bindings require it.
3366
+ fetch: (destination, init) => binding.fetch(new Request(destination, init))
3367
+ };
3368
+ }
3369
+ function fetchNetworkTarget(env, target, destination, init) {
3370
+ return resolveNetworkTargetTransport(env, target).fetch(destination, init);
3371
+ }
3372
+
3373
+ // src/worker-network-admission-state.ts
3374
+ var import_crm5 = require("@odla-ai/crm");
3375
+ var MAX_ASSIGNMENTS = 2e3;
3376
+ var admissionRows = (value) => Array.isArray(value) ? value : [];
3377
+ function assignmentId(targetId, personId) {
3378
+ return `leader-admission:${targetId}:${personId}`;
3379
+ }
3380
+ function deliveryId(targetId, environment) {
3381
+ return `leader-admission-delivery:${targetId}:${environment}`;
3382
+ }
3383
+ async function loadLeaderAdmissionAssignments(db, targetId) {
3384
+ const result = await db.query({ leaderAdmissionAssignments: { $: {
3385
+ where: { targetId },
3386
+ order: { updatedAt: "desc" },
3387
+ limit: MAX_ASSIGNMENTS
3388
+ } } });
3389
+ return admissionRows(result.leaderAdmissionAssignments);
3390
+ }
3391
+ async function loadLeaderAdmissionDelivery(db, targetId, environment) {
3392
+ const id = deliveryId(targetId, environment);
3393
+ const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id }, limit: 1 } } });
3394
+ return admissionRows(result.leaderAdmissionDeliveries)[0] ?? null;
3395
+ }
3396
+ function personAdmissionEmail(ctx, record4) {
3397
+ if (record4.type !== "person") return null;
3398
+ const field = ctx.chapter.crm.config.types.person?.emailField;
3399
+ if (!field) return null;
3400
+ const raw = record4.fields[field] ?? record4.primaryEmail;
3401
+ if (typeof raw !== "string") return null;
3402
+ const normalized = normalizeAdmissionEmail(raw);
3403
+ return isValidEmail(normalized) ? normalized : null;
3404
+ }
3405
+ async function grantInput(db, ctx, assignment) {
3406
+ const person = await (0, import_crm5.getRecord)({ crm: ctx.chapter.crm, db }, assignment.personId);
3407
+ const email = person ? personAdmissionEmail(ctx, person) : null;
3408
+ if (!email) throw new Error(`person ${assignment.personId} has no valid authoritative email`);
3409
+ return {
3410
+ id: assignment.grantId,
3411
+ email,
3412
+ decision: assignment.decision,
3413
+ ...assignment.note ? { note: assignment.note } : {},
3414
+ ...assignment.expiresAt !== void 0 ? { expiresAt: assignment.expiresAt } : {}
3415
+ };
3416
+ }
3417
+ async function persistLeaderAdmissionDesiredSet(opts) {
3418
+ const now = Date.now();
3419
+ const current = await loadLeaderAdmissionAssignments(opts.db, opts.target.id);
3420
+ const id = assignmentId(opts.target.id, opts.personId);
3421
+ const existing = current.find((row) => row.id === id);
3422
+ const digest = await signupControlDigest({
3423
+ targetId: opts.target.id,
3424
+ personId: opts.personId,
3425
+ decision: opts.decision,
3426
+ note: opts.note ?? null,
3427
+ expiresAt: opts.expiresAt ?? null,
3428
+ active: opts.active
3429
+ });
3430
+ const mutationReceiptId = `leader-admission-mutation:${opts.target.id}:${opts.mutationId}`;
3431
+ const priorMutation2 = await opts.db.query({ leaderAdmissionMutations: { $: {
3432
+ where: { id: mutationReceiptId },
3433
+ limit: 1
3434
+ } } });
3435
+ const receipt = admissionRows(priorMutation2.leaderAdmissionMutations)[0];
3436
+ if (receipt) {
3437
+ if (receipt.requestDigest !== digest || receipt.personId !== opts.personId) {
3438
+ throw new Error("mutation_conflict");
3439
+ }
3440
+ const saved = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
3441
+ if (!saved) throw new Error("delivery_missing");
3442
+ return { delivery: saved, duplicate: true };
3443
+ }
3444
+ if (existing?.lastMutationId === opts.mutationId) {
3445
+ if (existing.lastRequestDigest !== digest) throw new Error("mutation_conflict");
3446
+ const saved = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
3447
+ if (!saved) throw new Error("delivery_missing");
3448
+ return { delivery: saved, duplicate: true };
3449
+ }
3450
+ const assignment = {
3451
+ id,
3452
+ targetId: opts.target.id,
3453
+ personId: opts.personId,
3454
+ grantId: existing?.grantId ?? crypto.randomUUID().replaceAll("-", ""),
3455
+ decision: opts.decision,
3456
+ ...opts.note ? { note: opts.note } : {},
3457
+ ...opts.expiresAt !== void 0 ? { expiresAt: opts.expiresAt } : {},
3458
+ active: opts.active,
3459
+ lastMutationId: opts.mutationId,
3460
+ lastRequestDigest: digest,
3461
+ createdAt: existing?.createdAt ?? now,
3462
+ createdBy: existing?.createdBy ?? opts.actor,
3463
+ updatedAt: now,
3464
+ updatedBy: opts.actor
3465
+ };
3466
+ const desired = current.filter((row) => row.id !== id && row.active).concat(assignment.active ? [assignment] : []);
3467
+ const grants = await Promise.all(desired.map((row) => grantInput(opts.db, opts.ctx, row)));
3468
+ const previous = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
3469
+ const revision = (previous?.revision ?? 0) + 1;
3470
+ const envelope = await createAdmissionGrantEnvelope({
3471
+ schemaVersion: 1,
3472
+ revision,
3473
+ generatedAt: now,
3474
+ source: { appId: opts.ctx.chapter.id, actor: opts.actor },
3475
+ target: { chapterId: opts.target.id, environment: opts.env.ODLA_ENV },
3476
+ grants
3477
+ });
3478
+ const delivery = {
3479
+ id: deliveryId(opts.target.id, opts.env.ODLA_ENV),
3480
+ targetId: opts.target.id,
3481
+ environment: opts.env.ODLA_ENV,
3482
+ revision,
3483
+ digest: envelope.digest,
3484
+ envelope,
3485
+ status: "saved",
3486
+ attempts: 0,
3487
+ updatedAt: now
3488
+ };
3489
+ const ops = [
3490
+ { t: "update", ns: "leaderAdmissionAssignments", id, attrs: assignment },
3491
+ { t: "update", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: delivery },
3492
+ { t: "update", ns: "leaderAdmissionMutations", id: mutationReceiptId, attrs: {
3493
+ id: mutationReceiptId,
3494
+ targetId: opts.target.id,
3495
+ personId: opts.personId,
3496
+ requestDigest: digest,
3497
+ revision,
3498
+ createdAt: now
3499
+ } }
3500
+ ];
3501
+ const retract = [
3502
+ ...!opts.note && existing?.note !== void 0 ? ["note"] : [],
3503
+ ...opts.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
3504
+ ];
3505
+ if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id, attrs: retract });
3506
+ const tx = await opts.db.transact(ops, {
3507
+ mutationId: `leader-admission:${opts.target.id}:${opts.mutationId}`,
3508
+ guards: previous ? [{ ns: "leaderAdmissionDeliveries", id: previous.id, exists: true, equals: { revision: previous.revision } }] : [{ ns: "leaderAdmissionDeliveries", id: delivery.id, exists: false }]
3509
+ });
3510
+ return { delivery, duplicate: tx.duplicate };
3511
+ }
3512
+
3513
+ // src/worker-routes-network-admission.ts
3514
+ var PATH = "/api/admin/network/admissions";
3515
+ var RETRY_PATH = "/api/admin/network/admissions/retry";
3516
+ var REVOKE_PATH = "/api/admin/network/admissions/revoke";
3517
+ var MUTATION_ID = /^[A-Za-z0-9_-]{16,128}$/;
3518
+ var MAX_NOTE2 = 2e3;
3519
+ var reject4 = (status, code, error) => json({ ok: false, code, error }, status);
3520
+ async function gate2(req, env, ctx) {
3521
+ const db = ctx.makeDb(env);
3522
+ const user = await ctx.verifyUser(req, env);
3523
+ if (!user) return { response: reject4(401, "unauthorized", "unauthorized") };
3524
+ if (!await ctx.isAdmin(db, user)) return { response: reject4(403, "forbidden", "forbidden") };
3525
+ return { db, user };
3526
+ }
3527
+ function targetFor(ctx, id) {
3528
+ return ctx.chapter.network.targets.find((target) => target.id === id && target.admissionGrants);
3529
+ }
3530
+ async function deliverSaved(db, env, ctx, target, delivery) {
3531
+ if (delivery.status === "acknowledged") return delivery;
3532
+ const secret = await getVaultSecret(db, target.secretName);
3533
+ const attemptedAt = Date.now();
3534
+ const attempts = delivery.attempts + 1;
3535
+ let patch;
3536
+ if (!secret) {
3537
+ patch = {
3538
+ status: delivery.attempts > 0 ? "retrying" : "pending",
3539
+ attempts,
3540
+ lastAttemptAt: attemptedAt,
3541
+ lastError: "edge secret is missing",
3542
+ updatedAt: attemptedAt
3543
+ };
3544
+ } else {
3545
+ try {
3546
+ const receipt = await deliverAdmissionGrants({
3547
+ secret,
3548
+ sender: ctx.chapter.id,
3549
+ targetUrl: target.url,
3550
+ envelope: delivery.envelope,
3551
+ fetch: ((input, init) => fetchNetworkTarget(env, target, new URL(String(input)), init ?? {}))
3552
+ });
3553
+ patch = {
3554
+ status: "acknowledged",
3555
+ attempts,
3556
+ lastAttemptAt: attemptedAt,
3557
+ acknowledgedAt: receipt.acknowledgedAt,
3558
+ updatedAt: attemptedAt
3559
+ };
3560
+ } catch (error) {
3561
+ const permanent = error instanceof AdmissionGrantDeliveryError && error.status >= 400 && error.status < 500;
3562
+ patch = {
3563
+ status: permanent ? "rejected" : delivery.attempts > 0 ? "retrying" : "pending",
3564
+ attempts,
3565
+ lastAttemptAt: attemptedAt,
3566
+ lastError: error instanceof Error ? error.message.slice(0, 2e3) : "delivery failed",
3567
+ updatedAt: attemptedAt
3568
+ };
3569
+ }
3570
+ }
3571
+ const ops = [{
3572
+ t: "update",
3573
+ ns: "leaderAdmissionDeliveries",
3574
+ id: delivery.id,
3575
+ attrs: patch
3576
+ }];
3577
+ if (patch.status === "acknowledged" && delivery.lastError) {
3578
+ ops.push({ t: "retract", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: ["lastError"] });
3579
+ }
3580
+ await db.transact(ops, {
3581
+ mutationId: `leader-admission-attempt:${delivery.targetId}:${delivery.revision}:${attempts}:${patch.status}`
3582
+ });
3583
+ return { ...delivery, ...patch };
3584
+ }
3585
+ async function parseBody(req) {
3586
+ try {
3587
+ return await req.json();
3588
+ } catch {
3589
+ return null;
3590
+ }
3591
+ }
3592
+ var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
3593
+ if (![PATH, RETRY_PATH, REVOKE_PATH].includes(url.pathname)) return null;
3594
+ const auth = await gate2(req, env, ctx);
3595
+ if ("response" in auth) return auth.response;
3596
+ const db = auth.db;
3597
+ if (url.pathname === PATH && req.method === "GET") {
3598
+ const targetId2 = url.searchParams.get("targetId") ?? "";
3599
+ const target2 = targetFor(ctx, targetId2);
3600
+ if (!target2) return reject4(404, "unknown_target", "admission target is not configured");
3601
+ return json({
3602
+ ok: true,
3603
+ target: { id: target2.id, name: target2.name },
3604
+ assignments: await loadLeaderAdmissionAssignments(db, target2.id),
3605
+ delivery: await loadLeaderAdmissionDelivery(db, target2.id, env.ODLA_ENV)
3606
+ });
3607
+ }
3608
+ if (req.method !== "POST") return null;
3609
+ const body = await parseBody(req);
3610
+ if (!body) return reject4(422, "invalid_request", "request body must be JSON");
3611
+ const targetId = typeof body.targetId === "string" ? body.targetId : "";
3612
+ const target = targetFor(ctx, targetId);
3613
+ if (!target) return reject4(404, "unknown_target", "admission target is not configured");
3614
+ if (url.pathname === RETRY_PATH) {
3615
+ const delivery = await loadLeaderAdmissionDelivery(db, target.id, env.ODLA_ENV);
3616
+ if (!delivery) return reject4(404, "not_found", "no saved admission delivery exists");
3617
+ return json({ ok: true, delivery: await deliverSaved(db, env, ctx, target, delivery) });
3618
+ }
3619
+ const personId = typeof body.personId === "string" ? body.personId : "";
3620
+ const mutationId = typeof body.mutationId === "string" ? body.mutationId : "";
3621
+ if (!personId || !MUTATION_ID.test(mutationId)) {
3622
+ return reject4(422, "invalid_request", "personId and a stable mutationId are required");
3623
+ }
3624
+ const person = await (0, import_crm6.getRecord)({ crm: ctx.chapter.crm, db: auth.db }, personId);
3625
+ if (!person) return reject4(404, "person_not_found", "person not found");
3626
+ if (!personAdmissionEmail(ctx, person)) {
3627
+ return reject4(422, "invalid_email", "person has no valid authoritative email");
3628
+ }
3629
+ const decision = body.decision ?? "waive_interview";
3630
+ if (decision !== "waive_interview" && decision !== "require_interview") {
3631
+ return reject4(422, "invalid_decision", "decision must be waive_interview or require_interview");
3632
+ }
3633
+ const note = typeof body.note === "string" ? body.note.trim() : "";
3634
+ if (note.length > MAX_NOTE2) return reject4(422, "invalid_note", "note is too long");
3635
+ const expiresAt = body.expiresAt == null ? void 0 : Number(body.expiresAt);
3636
+ if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= Date.now())) {
3637
+ return reject4(422, "invalid_expiry", "expiresAt must be a future epoch timestamp");
3638
+ }
3639
+ try {
3640
+ const saved = await persistLeaderAdmissionDesiredSet({
3641
+ db,
3642
+ ctx,
3643
+ env,
3644
+ target,
3645
+ personId,
3646
+ decision,
3647
+ ...note ? { note } : {},
3648
+ ...expiresAt !== void 0 ? { expiresAt } : {},
3649
+ active: url.pathname !== REVOKE_PATH,
3650
+ mutationId,
3651
+ actor: auth.user.email ?? auth.user.userId
3652
+ });
3653
+ const delivery = await deliverSaved(db, env, ctx, target, saved.delivery);
3654
+ return json({ ok: true, duplicate: saved.duplicate, delivery }, delivery.status === "acknowledged" ? 200 : 202);
3655
+ } catch (error) {
3656
+ if (error instanceof Error && error.message === "mutation_conflict") {
3657
+ return reject4(409, "mutation_conflict", "mutationId was already used for another request");
3658
+ }
3659
+ if (error instanceof Error && /valid authoritative email/.test(error.message)) {
3660
+ return reject4(422, "invalid_email", error.message);
3661
+ }
3662
+ return reject4(409, "revision_conflict", "admission assignments changed; reload and retry");
3663
+ }
3664
+ };
3665
+ async function retryLeaderAdmissionDeliveries(env, ctx, limit = 100) {
3666
+ const db = ctx.makeDb(env);
3667
+ const result = await db.query({ leaderAdmissionDeliveries: { $: { limit } } });
3668
+ const pending = admissionRows(result.leaderAdmissionDeliveries).filter((delivery) => delivery.status !== "acknowledged" && delivery.status !== "rejected").slice(0, Math.max(1, Math.min(limit, 500)));
3669
+ const delivered = [];
3670
+ for (const item of pending) {
3671
+ const target = targetFor(ctx, item.targetId);
3672
+ if (target) delivered.push(await deliverSaved(db, env, ctx, target, item));
3673
+ }
3674
+ return delivered;
3675
+ }
3676
+
3287
3677
  // src/worker-routes-schedule.ts
3288
3678
  var import_calendar = require("@odla-ai/calendar");
3289
3679
  function errCode(err) {
@@ -4545,16 +4935,16 @@ var crmDeps2 = (db, ctx) => ({
4545
4935
  });
4546
4936
  var handleAdminCrmSync = async (req, url, env, ctx) => {
4547
4937
  if (req.method !== "POST" || url.pathname !== "/api/admin/crm/sync") return null;
4548
- const gate9 = await adminGate(req, env, ctx);
4549
- if (gate9 instanceof Response) return gate9;
4550
- const result = await backfillCrm(crmDeps2(gate9.db, ctx));
4938
+ const gate10 = await adminGate(req, env, ctx);
4939
+ if (gate10 instanceof Response) return gate10;
4940
+ const result = await backfillCrm(crmDeps2(gate10.db, ctx));
4551
4941
  return json({ ok: true, ...result });
4552
4942
  };
4553
4943
  var handleAdminPeople = async (req, url, env, ctx) => {
4554
4944
  if (req.method !== "GET" || url.pathname !== "/api/admin/people") return null;
4555
- const gate9 = await adminGate(req, env, ctx);
4556
- if (gate9 instanceof Response) return gate9;
4557
- const { db } = gate9;
4945
+ const gate10 = await adminGate(req, env, ctx);
4946
+ if (gate10 instanceof Response) return gate10;
4947
+ const { db } = gate10;
4558
4948
  const sk = await getVaultSecret(db, "clerk_secret_key");
4559
4949
  const [data, roleList] = await Promise.all([
4560
4950
  db.query({
@@ -4607,9 +4997,9 @@ var handleAdminPeople = async (req, url, env, ctx) => {
4607
4997
  };
4608
4998
  var handleAdminPeopleAccess = async (req, url, env, ctx) => {
4609
4999
  if (req.method !== "GET" || url.pathname !== "/api/admin/people/access") return null;
4610
- const gate9 = await adminGate(req, env, ctx);
4611
- if (gate9 instanceof Response) return gate9;
4612
- const { db } = gate9;
5000
+ const gate10 = await adminGate(req, env, ctx);
5001
+ if (gate10 instanceof Response) return gate10;
5002
+ const { db } = gate10;
4613
5003
  const targetId = url.searchParams.get("userId") ?? "";
4614
5004
  if (!targetId.startsWith("user_")) return json({ error: "invalid userId" }, 400);
4615
5005
  const sk = await getVaultSecret(db, "clerk_secret_key");
@@ -4620,9 +5010,9 @@ var handleAdminPeopleAccess = async (req, url, env, ctx) => {
4620
5010
  };
4621
5011
  var handleAdminPeopleRole = async (req, url, env, ctx) => {
4622
5012
  if (req.method !== "POST" || url.pathname !== "/api/admin/people/role") return null;
4623
- const gate9 = await adminGate(req, env, ctx);
4624
- if (gate9 instanceof Response) return gate9;
4625
- const { db, actor } = gate9;
5013
+ const gate10 = await adminGate(req, env, ctx);
5014
+ if (gate10 instanceof Response) return gate10;
5015
+ const { db, actor } = gate10;
4626
5016
  let body;
4627
5017
  try {
4628
5018
  body = await req.json();
@@ -4769,7 +5159,7 @@ function subAnnualCents(sub) {
4769
5159
  }
4770
5160
 
4771
5161
  // src/worker-routes-admin-dashboard.ts
4772
- async function gate2(req, env, ctx) {
5162
+ async function gate3(req, env, ctx) {
4773
5163
  const rawDb = ctx.makeDb(env);
4774
5164
  const u = await ctx.verifyUser(req, env);
4775
5165
  if (!u) return json({ error: "unauthorized" }, 401);
@@ -4779,7 +5169,7 @@ async function gate2(req, env, ctx) {
4779
5169
  var rows3 = (v) => Array.isArray(v) ? v : [];
4780
5170
  var handleAdminDashboard = async (req, url, env, ctx) => {
4781
5171
  if (req.method !== "GET" || url.pathname !== "/api/admin/dashboard") return null;
4782
- const got = await gate2(req, env, ctx);
5172
+ const got = await gate3(req, env, ctx);
4783
5173
  if (got instanceof Response) return got;
4784
5174
  const db = got;
4785
5175
  const now = Date.now();
@@ -4862,7 +5252,7 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
4862
5252
  };
4863
5253
  var handleAdminBilling = async (req, url, env, ctx) => {
4864
5254
  if (req.method !== "GET" || url.pathname !== "/api/admin/billing") return null;
4865
- const got = await gate2(req, env, ctx);
5255
+ const got = await gate3(req, env, ctx);
4866
5256
  if (got instanceof Response) return got;
4867
5257
  const db = got;
4868
5258
  const sk = await getVaultSecret(db, "stripe_secret_key");
@@ -4922,7 +5312,7 @@ var handleAdminBilling = async (req, url, env, ctx) => {
4922
5312
 
4923
5313
  // src/worker-routes-admin-lifecycle.ts
4924
5314
  var import_calendar3 = require("@odla-ai/calendar");
4925
- async function gate3(req, env, ctx) {
5315
+ async function gate4(req, env, ctx) {
4926
5316
  const rawDb = ctx.makeDb(env);
4927
5317
  const u = await ctx.verifyUser(req, env);
4928
5318
  if (!u) return json({ error: "unauthorized" }, 401);
@@ -4948,7 +5338,7 @@ async function loadGroup(db, id) {
4948
5338
  var handleAdminMeetingReschedule = async (req, url, env, ctx) => {
4949
5339
  const m = url.pathname.match(/^\/api\/admin\/meetings\/([0-9a-f-]+)\/reschedule$/);
4950
5340
  if (req.method !== "POST" || !m) return null;
4951
- const got = await gate3(req, env, ctx);
5341
+ const got = await gate4(req, env, ctx);
4952
5342
  if (got instanceof Response) return got;
4953
5343
  const db = got;
4954
5344
  const body = await readJson(req);
@@ -4989,7 +5379,7 @@ var handleAdminMeetingReschedule = async (req, url, env, ctx) => {
4989
5379
  var handleAdminMeetingCancel = async (req, url, env, ctx) => {
4990
5380
  const m = url.pathname.match(/^\/api\/admin\/meetings\/([0-9a-f-]+)\/cancel$/);
4991
5381
  if (req.method !== "POST" || !m) return null;
4992
- const got = await gate3(req, env, ctx);
5382
+ const got = await gate4(req, env, ctx);
4993
5383
  if (got instanceof Response) return got;
4994
5384
  const db = got;
4995
5385
  const { meetings } = await db.query({ meetings: { $: { where: { id: m[1] }, limit: 1 } } });
@@ -5012,7 +5402,7 @@ var handleAdminMeetingCancel = async (req, url, env, ctx) => {
5012
5402
  var handleAdminApprove = async (req, url, env, ctx) => {
5013
5403
  const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)\/approve$/);
5014
5404
  if (req.method !== "POST" || !m) return null;
5015
- const got = await gate3(req, env, ctx);
5405
+ const got = await gate4(req, env, ctx);
5016
5406
  if (got instanceof Response) return got;
5017
5407
  const db = got;
5018
5408
  const id = m[1];
@@ -5025,7 +5415,7 @@ var handleAdminApprove = async (req, url, env, ctx) => {
5025
5415
  var handleAdminRefund = async (req, url, env, ctx) => {
5026
5416
  const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)\/refund$/);
5027
5417
  if (req.method !== "POST" || !m) return null;
5028
- const got = await gate3(req, env, ctx);
5418
+ const got = await gate4(req, env, ctx);
5029
5419
  if (got instanceof Response) return got;
5030
5420
  const db = got;
5031
5421
  const app = await loadApp(db, m[1]);
@@ -5057,7 +5447,7 @@ var handleAdminRefund = async (req, url, env, ctx) => {
5057
5447
  var handleAdminApplicationPatch = async (req, url, env, ctx) => {
5058
5448
  const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)$/);
5059
5449
  if (req.method !== "PATCH" || !m) return null;
5060
- const got = await gate3(req, env, ctx);
5450
+ const got = await gate4(req, env, ctx);
5061
5451
  if (got instanceof Response) return got;
5062
5452
  const db = got;
5063
5453
  const id = m[1];
@@ -5085,7 +5475,7 @@ var handleAdminApplicationPatch = async (req, url, env, ctx) => {
5085
5475
  };
5086
5476
 
5087
5477
  // src/worker-routes-admin-comms.ts
5088
- async function gate4(req, env, ctx) {
5478
+ async function gate5(req, env, ctx) {
5089
5479
  const rawDb = ctx.makeDb(env);
5090
5480
  const u = await ctx.verifyUser(req, env);
5091
5481
  if (!u) return json({ error: "unauthorized" }, 401);
@@ -5107,7 +5497,7 @@ var notifyDeps = (db, env) => ({
5107
5497
  });
5108
5498
  var handleAdminGroupEmail = async (req, url, env, ctx) => {
5109
5499
  if (url.pathname !== "/api/admin/group/email" || req.method !== "GET" && req.method !== "PUT") return null;
5110
- const got = await gate4(req, env, ctx);
5500
+ const got = await gate5(req, env, ctx);
5111
5501
  if (got instanceof Response) return got;
5112
5502
  const db = got;
5113
5503
  const group = await loadGroup2(db, ctx.chapter.id);
@@ -5169,7 +5559,7 @@ var handleAdminGroupEmail = async (req, url, env, ctx) => {
5169
5559
  };
5170
5560
  var handleAdminEmailLog = async (req, url, env, ctx) => {
5171
5561
  if (req.method !== "GET" || url.pathname !== "/api/admin/email/log") return null;
5172
- const got = await gate4(req, env, ctx);
5562
+ const got = await gate5(req, env, ctx);
5173
5563
  if (got instanceof Response) return got;
5174
5564
  const { emailLog } = await got.query({ emailLog: { $: { order: { sentAt: "desc" }, limit: 50 } } });
5175
5565
  const sends = (emailLog ?? []).map((r) => ({
@@ -5186,7 +5576,7 @@ var handleAdminEmailLog = async (req, url, env, ctx) => {
5186
5576
  };
5187
5577
  var handleAdminEmailTest = async (req, url, env, ctx) => {
5188
5578
  if (req.method !== "POST" || url.pathname !== "/api/admin/email/test") return null;
5189
- const got = await gate4(req, env, ctx);
5579
+ const got = await gate5(req, env, ctx);
5190
5580
  if (got instanceof Response) return got;
5191
5581
  const db = got;
5192
5582
  let body;
@@ -5214,7 +5604,7 @@ var handleAdminEmailTest = async (req, url, env, ctx) => {
5214
5604
  var handleAdminComms = async (req, url, env, ctx) => {
5215
5605
  const m = url.pathname.match(/^\/api\/admin\/people\/([0-9a-fA-F-]+)\/comms$/);
5216
5606
  if (req.method !== "GET" || !m) return null;
5217
- const got = await gate4(req, env, ctx);
5607
+ const got = await gate5(req, env, ctx);
5218
5608
  if (got instanceof Response) return got;
5219
5609
  const db = got;
5220
5610
  const appId = m[1];
@@ -5252,26 +5642,12 @@ var handleAdminComms = async (req, url, env, ctx) => {
5252
5642
  };
5253
5643
 
5254
5644
  // src/worker-routes-network.ts
5255
- var import_crm6 = require("@odla-ai/crm");
5256
- var import_db7 = require("@odla-ai/db");
5257
-
5258
- // src/worker-network-fetch.ts
5259
- function targetFetcher(env, target) {
5260
- if (!target.binding) return null;
5261
- const candidate = env[target.binding];
5262
- if (typeof candidate !== "object" || candidate === null || !("fetch" in candidate) || typeof candidate.fetch !== "function") {
5263
- throw new Error(`service binding "${target.binding}" is unavailable`);
5264
- }
5265
- return candidate;
5266
- }
5267
- function fetchNetworkTarget(env, target, destination, init) {
5268
- const binding = targetFetcher(env, target);
5269
- return binding ? binding.fetch(new Request(destination, init)) : globalThis.fetch(destination, init);
5270
- }
5645
+ var import_crm8 = require("@odla-ai/crm");
5646
+ var import_db8 = require("@odla-ai/db");
5271
5647
 
5272
5648
  // src/worker-network-push.ts
5273
- var import_crm5 = require("@odla-ai/crm");
5274
- var import_db6 = require("@odla-ai/db");
5649
+ var import_crm7 = require("@odla-ai/crm");
5650
+ var import_db7 = require("@odla-ai/db");
5275
5651
  async function pushNetworkRecord(db, env, ctx, target, record4) {
5276
5652
  const secret = await getVaultSecret(db, target.secretName);
5277
5653
  const crmDeps3 = {
@@ -5282,7 +5658,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
5282
5658
  };
5283
5659
  if (!secret) {
5284
5660
  const error = `vault secret "${target.secretName}" is missing`;
5285
- await (0, import_crm5.recordDeliveryAttempt)(crmDeps3, {
5661
+ await (0, import_crm7.recordDeliveryAttempt)(crmDeps3, {
5286
5662
  recordId: record4.id,
5287
5663
  targetId: target.id,
5288
5664
  status: "failed",
@@ -5305,7 +5681,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
5305
5681
  const payloadBody = JSON.stringify(payload);
5306
5682
  const destination = new URL("/api/network/shared", target.url);
5307
5683
  try {
5308
- const signed = await (0, import_db6.signFederatedRequest)({
5684
+ const signed = await (0, import_db7.signFederatedRequest)({
5309
5685
  secret,
5310
5686
  sender: ctx.chapter.id,
5311
5687
  method: "POST",
@@ -5321,7 +5697,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
5321
5697
  const responseBody = await res.json().catch(() => ({}));
5322
5698
  if (!res.ok) {
5323
5699
  const error = responseBody.error ?? "follower rejected the record";
5324
- await (0, import_crm5.recordDeliveryAttempt)(crmDeps3, {
5700
+ await (0, import_crm7.recordDeliveryAttempt)(crmDeps3, {
5325
5701
  recordId: record4.id,
5326
5702
  targetId: target.id,
5327
5703
  status: "failed",
@@ -5336,7 +5712,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
5336
5712
  error
5337
5713
  };
5338
5714
  }
5339
- await (0, import_crm5.recordDeliveryAttempt)(crmDeps3, {
5715
+ await (0, import_crm7.recordDeliveryAttempt)(crmDeps3, {
5340
5716
  recordId: record4.id,
5341
5717
  targetId: target.id,
5342
5718
  status: "delivered",
@@ -5352,7 +5728,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
5352
5728
  };
5353
5729
  } catch (err) {
5354
5730
  const error = err instanceof Error ? err.message : "delivery failed";
5355
- await (0, import_crm5.recordDeliveryAttempt)(crmDeps3, {
5731
+ await (0, import_crm7.recordDeliveryAttempt)(crmDeps3, {
5356
5732
  recordId: record4.id,
5357
5733
  targetId: target.id,
5358
5734
  status: "failed",
@@ -5364,7 +5740,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
5364
5740
  }
5365
5741
 
5366
5742
  // src/worker-routes-network.ts
5367
- async function gate5(req, env, ctx) {
5743
+ async function gate6(req, env, ctx) {
5368
5744
  const db = ctx.makeDb(env);
5369
5745
  const user = await ctx.verifyUser(req, env);
5370
5746
  if (!user) return { response: json({ error: "unauthorized" }, 401) };
@@ -5373,7 +5749,7 @@ async function gate5(req, env, ctx) {
5373
5749
  }
5374
5750
  var handleAdminNetworkTargets = async (req, url, env, ctx) => {
5375
5751
  if (req.method !== "GET" || url.pathname !== "/api/admin/network/targets") return null;
5376
- const got = await gate5(req, env, ctx);
5752
+ const got = await gate6(req, env, ctx);
5377
5753
  if ("response" in got) return got.response;
5378
5754
  return json({
5379
5755
  targets: ctx.chapter.network.targets.map(({
@@ -5400,31 +5776,55 @@ var handleNetworkSnapshot = async (req, url, env, ctx) => {
5400
5776
  const db = ctx.makeDb(env);
5401
5777
  const secret = await getVaultSecret(db, "network_share_secret");
5402
5778
  if (!secret) return json({ error: "unauthorized" }, 401);
5403
- const verified = await (0, import_db7.verifyFederatedRequest)(req, { secret });
5779
+ const verified = await (0, import_db8.verifyFederatedRequest)(req, { secret });
5404
5780
  if (!verified.ok) return json({ error: "unauthorized", reason: verified.reason }, 401);
5405
5781
  const types = await Promise.all(
5406
- Object.keys(ctx.chapter.crm.config.types).map((type) => (0, import_crm6.typeSummary)({ crm: ctx.chapter.crm, db }, type))
5782
+ Object.keys(ctx.chapter.crm.config.types).map((type) => (0, import_crm8.typeSummary)({ crm: ctx.chapter.crm, db }, type))
5407
5783
  );
5784
+ const activeConfiguration = ctx.chapter.signupControl ? await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV) : null;
5408
5785
  const snapshot = {
5409
5786
  version: 1,
5410
5787
  site: { id: ctx.chapter.id, name: ctx.chapter.name, mode: ctx.chapter.mode },
5411
5788
  generatedAt: Date.now(),
5789
+ configuration: {
5790
+ activeRevision: activeConfiguration?.revision ?? null,
5791
+ ...activeConfiguration ? { digest: activeConfiguration.digest } : {}
5792
+ },
5412
5793
  types
5413
5794
  };
5414
5795
  return json(snapshot);
5415
5796
  };
5416
5797
  async function snapshotOne(db, env, ctx, target) {
5798
+ const transportKind = target.binding ? "binding" : "url";
5799
+ const base = {
5800
+ id: target.id,
5801
+ name: target.name,
5802
+ url: target.url,
5803
+ configuration: { activeRevision: null },
5804
+ publicSite: { available: null }
5805
+ };
5417
5806
  const secret = await getVaultSecret(db, target.secretName);
5418
- if (!secret) return { id: target.id, name: target.name, url: target.url, available: false, error: "edge secret is missing" };
5807
+ if (!secret) return {
5808
+ ...base,
5809
+ available: false,
5810
+ transport: {
5811
+ kind: transportKind,
5812
+ ...target.binding ? { binding: target.binding } : {},
5813
+ reachable: null,
5814
+ error: { code: "edge_secret_missing", message: "edge secret is missing" }
5815
+ },
5816
+ error: "edge secret is missing"
5817
+ };
5419
5818
  const destination = new URL("/api/network/snapshot", target.url);
5420
5819
  try {
5421
- const headers = await (0, import_db7.signFederatedRequest)({
5820
+ const transport = resolveNetworkTargetTransport(env, target);
5821
+ const headers = await (0, import_db8.signFederatedRequest)({
5422
5822
  secret,
5423
5823
  sender: ctx.chapter.id,
5424
5824
  method: "GET",
5425
5825
  url: destination
5426
5826
  });
5427
- const response = await fetchNetworkTarget(env, target, destination, {
5827
+ const response = await transport.fetch(destination, {
5428
5828
  headers,
5429
5829
  signal: AbortSignal.timeout(1e4)
5430
5830
  });
@@ -5432,34 +5832,53 @@ async function snapshotOne(db, env, ctx, target) {
5432
5832
  if (!response.ok || !body || !("version" in body) || body.version !== 1 || !("site" in body) || body.site.id !== target.id || !Array.isArray(body.types)) {
5433
5833
  const upstream = body && "error" in body && typeof body.error === "string" ? body.error : void 0;
5434
5834
  return {
5435
- id: target.id,
5436
- name: target.name,
5437
- url: target.url,
5835
+ ...base,
5438
5836
  available: false,
5837
+ transport: {
5838
+ kind: transport.kind,
5839
+ ...transport.binding ? { binding: transport.binding } : {},
5840
+ reachable: true
5841
+ },
5842
+ publicSite: { available: transport.kind === "url" ? true : null },
5439
5843
  error: upstream ?? (response.ok ? "invalid follower snapshot" : `follower returned ${response.status}`)
5440
5844
  };
5441
5845
  }
5442
5846
  return {
5443
- id: target.id,
5444
- name: target.name,
5445
- url: target.url,
5847
+ ...base,
5446
5848
  available: true,
5849
+ transport: {
5850
+ kind: transport.kind,
5851
+ ...transport.binding ? { binding: transport.binding } : {},
5852
+ reachable: true
5853
+ },
5854
+ configuration: body.configuration ?? { activeRevision: null },
5855
+ publicSite: { available: transport.kind === "url" ? true : null },
5447
5856
  generatedAt: body.generatedAt,
5448
5857
  types: body.types
5449
5858
  };
5450
5859
  } catch (error) {
5860
+ const bindingError = error instanceof NetworkTargetTransportError;
5861
+ const message = error instanceof Error ? error.message : "snapshot failed";
5451
5862
  return {
5452
- id: target.id,
5453
- name: target.name,
5454
- url: target.url,
5863
+ ...base,
5455
5864
  available: false,
5456
- error: error instanceof Error ? error.message : "snapshot failed"
5865
+ transport: {
5866
+ kind: transportKind,
5867
+ ...target.binding ? { binding: target.binding } : {},
5868
+ reachable: false,
5869
+ error: {
5870
+ code: bindingError ? "binding_unavailable" : target.binding ? "binding_request_failed" : "url_request_failed",
5871
+ message
5872
+ }
5873
+ },
5874
+ publicSite: { available: target.binding ? null : false },
5875
+ error: message
5457
5876
  };
5458
5877
  }
5459
5878
  }
5460
5879
  var handleAdminNetworkRollup = async (req, url, env, ctx) => {
5461
5880
  if (req.method !== "GET" || url.pathname !== "/api/admin/network/rollup") return null;
5462
- const got = await gate5(req, env, ctx);
5881
+ const got = await gate6(req, env, ctx);
5463
5882
  if ("response" in got) return got.response;
5464
5883
  const targets = await Promise.all(
5465
5884
  ctx.chapter.network.targets.map((target) => snapshotOne(got.db, env, ctx, target))
@@ -5486,7 +5905,7 @@ var handleAdminNetworkRollup = async (req, url, env, ctx) => {
5486
5905
  };
5487
5906
  var handleAdminNetworkPush = async (req, url, env, ctx) => {
5488
5907
  if (req.method !== "POST" || url.pathname !== "/api/admin/network/push") return null;
5489
- const got = await gate5(req, env, ctx);
5908
+ const got = await gate6(req, env, ctx);
5490
5909
  if ("response" in got) return got.response;
5491
5910
  let body;
5492
5911
  try {
@@ -5501,7 +5920,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
5501
5920
  if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
5502
5921
  const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
5503
5922
  if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
5504
- const record4 = await (0, import_crm6.getRecord)({ crm: ctx.chapter.crm, db: got.db }, body.recordId);
5923
+ const record4 = await (0, import_crm8.getRecord)({ crm: ctx.chapter.crm, db: got.db }, body.recordId);
5505
5924
  if (!record4) return json({ error: "record not found" }, 404);
5506
5925
  const results = await Promise.all(
5507
5926
  targets.map((target) => pushNetworkRecord(got.db, env, ctx, target, record4))
@@ -5510,8 +5929,8 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
5510
5929
  };
5511
5930
 
5512
5931
  // src/worker-routes-network-records-follower.ts
5513
- var import_crm7 = require("@odla-ai/crm");
5514
- var import_db8 = require("@odla-ai/db");
5932
+ var import_crm9 = require("@odla-ai/crm");
5933
+ var import_db9 = require("@odla-ai/db");
5515
5934
 
5516
5935
  // src/worker-routes-network-records-common.ts
5517
5936
  var IDENTIFIER = /^[a-z][a-zA-Z0-9_]*$/;
@@ -5520,7 +5939,7 @@ var MAX_OFFSET = 1e4;
5520
5939
  var MAX_SEARCH = 200;
5521
5940
  var MAX_BODY = 32768;
5522
5941
  var RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9:_-]{0,191}$/;
5523
- var MUTATION_ID = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
5942
+ var MUTATION_ID2 = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
5524
5943
  function bodyTooLarge(body) {
5525
5944
  return new TextEncoder().encode(body).byteLength > MAX_BODY;
5526
5945
  }
@@ -5594,16 +6013,16 @@ function mutationError(error) {
5594
6013
  if (error?.code === "transact_guard_failed") {
5595
6014
  return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
5596
6015
  }
5597
- if (error instanceof import_crm7.CrmInputError) {
6016
+ if (error instanceof import_crm9.CrmInputError) {
5598
6017
  return json({ error: error.message, code: "invalid_transition_or_fields", ...error.fields ? { fields: error.fields } : {} }, 422);
5599
6018
  }
5600
6019
  return json({ error: "follower mutation failed", code: "mutation_failed" }, 500);
5601
6020
  }
5602
6021
  async function priorMutation(db, id) {
5603
6022
  const result = await db.query({
5604
- [import_crm7.CRM_NS.activity]: { $: { where: { id }, limit: 1 } }
6023
+ [import_crm9.CRM_NS.activity]: { $: { where: { id }, limit: 1 } }
5605
6024
  });
5606
- const row = (result[import_crm7.CRM_NS.activity] ?? [])[0];
6025
+ const row = (result[import_crm9.CRM_NS.activity] ?? [])[0];
5607
6026
  const meta = row?.meta;
5608
6027
  const receipt = meta?.[NETWORK_MUTATION_META];
5609
6028
  return typeof receipt?.fingerprint === "string" ? { fingerprint: receipt.fingerprint } : null;
@@ -5617,7 +6036,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5617
6036
  const secret = await getVaultSecret(db, "network_share_secret");
5618
6037
  const readers = ctx.chapter.network.readers;
5619
6038
  if (!secret || readers.length === 0) return json({ error: "unauthorized" }, 401);
5620
- const verified = await (0, import_db8.verifyFederatedRequest)(req, {
6039
+ const verified = await (0, import_db9.verifyFederatedRequest)(req, {
5621
6040
  secret,
5622
6041
  senders: readers.map((reader2) => reader2.id)
5623
6042
  });
@@ -5638,7 +6057,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5638
6057
  const hasFields = Boolean(fields && typeof fields === "object" && !Array.isArray(fields));
5639
6058
  const hasStage = typeof input?.stage === "string";
5640
6059
  const actor = input?.actor;
5641
- if (!input || !IDENTIFIER.test(input.type) || !RECORD_ID.test(input.recordId) || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || !MUTATION_ID.test(input.mutationId) || !actor || typeof actor.id !== "string" || !RECORD_ID.test(actor.id) || hasFields === hasStage) {
6060
+ if (!input || !IDENTIFIER.test(input.type) || !RECORD_ID.test(input.recordId) || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || !MUTATION_ID2.test(input.mutationId) || !actor || typeof actor.id !== "string" || !RECORD_ID.test(actor.id) || hasFields === hasStage) {
5642
6061
  return json({ error: "invalid network mutation", code: "invalid_mutation" }, 422);
5643
6062
  }
5644
6063
  const editable = reader.editableFields[input.type];
@@ -5657,7 +6076,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5657
6076
  if (prior.fingerprint !== fingerprint) {
5658
6077
  return json({ error: "mutationId was already used for different input", code: "mutation_conflict" }, 409);
5659
6078
  }
5660
- const replayed = await (0, import_crm7.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
6079
+ const replayed = await (0, import_crm9.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
5661
6080
  if (!replayed || replayed.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
5662
6081
  const result = {
5663
6082
  version: 1,
@@ -5667,7 +6086,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5667
6086
  };
5668
6087
  return json(result);
5669
6088
  }
5670
- const record4 = await (0, import_crm7.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
6089
+ const record4 = await (0, import_crm9.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
5671
6090
  if (!record4 || record4.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
5672
6091
  if (record4.updatedAt !== input.expectedRevision) {
5673
6092
  return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
@@ -5688,7 +6107,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5688
6107
  };
5689
6108
  try {
5690
6109
  const deps = { crm: ctx.chapter.crm, db };
5691
- const updated = hasFields ? await (0, import_crm7.updateRecord)(deps, {
6110
+ const updated = hasFields ? await (0, import_crm9.updateRecord)(deps, {
5692
6111
  id: record4.id,
5693
6112
  input: fields,
5694
6113
  mutationId: activityId,
@@ -5699,7 +6118,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5699
6118
  authorId: `network:${verified.sender}`,
5700
6119
  meta: auditMeta
5701
6120
  }
5702
- }) : await (0, import_crm7.setStage)(deps, {
6121
+ }) : await (0, import_crm9.setStage)(deps, {
5703
6122
  id: record4.id,
5704
6123
  to: input.stage,
5705
6124
  authorId: `network:${verified.sender}`,
@@ -5729,7 +6148,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5729
6148
  if (limit == null || offset == null || search.length > MAX_SEARCH) {
5730
6149
  return json({ error: "invalid pagination or search parameters" }, 400);
5731
6150
  }
5732
- const page = await (0, import_crm7.listRecords)(
6151
+ const page = await (0, import_crm9.listRecords)(
5733
6152
  { crm: ctx.chapter.crm, db },
5734
6153
  {
5735
6154
  type,
@@ -5752,15 +6171,15 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5752
6171
  };
5753
6172
 
5754
6173
  // src/worker-routes-network-records-admin.ts
5755
- var import_db9 = require("@odla-ai/db");
5756
- async function gate6(req, env, ctx) {
6174
+ var import_db10 = require("@odla-ai/db");
6175
+ async function gate7(req, env, ctx) {
5757
6176
  const db = ctx.makeDb(env);
5758
6177
  const user = await ctx.verifyUser(req, env);
5759
6178
  if (!user) return { response: json({ error: "unauthorized" }, 401) };
5760
6179
  if (!await ctx.isAdmin(db, user)) return { response: json({ error: "forbidden" }, 403) };
5761
6180
  return { db, user };
5762
6181
  }
5763
- function targetFor(ctx, id) {
6182
+ function targetFor2(ctx, id) {
5764
6183
  return ctx.chapter.network.targets.find((target) => target.id === id);
5765
6184
  }
5766
6185
  async function readTargetRecords(db, env, ctx, target, query) {
@@ -5769,7 +6188,7 @@ async function readTargetRecords(db, env, ctx, target, query) {
5769
6188
  const destination = new URL("/api/network/records", target.url);
5770
6189
  destination.search = query.toString();
5771
6190
  try {
5772
- const headers = await (0, import_db9.signFederatedRequest)({ secret, sender: ctx.chapter.id, method: "GET", url: destination });
6191
+ const headers = await (0, import_db10.signFederatedRequest)({ secret, sender: ctx.chapter.id, method: "GET", url: destination });
5773
6192
  const response = await fetchNetworkTarget(env, target, destination, {
5774
6193
  headers,
5775
6194
  signal: AbortSignal.timeout(1e4)
@@ -5805,7 +6224,7 @@ async function mutateTargetRecord(db, env, ctx, target, input) {
5805
6224
  const destination = new URL("/api/network/records", target.url);
5806
6225
  const body = JSON.stringify(input);
5807
6226
  try {
5808
- const headers = await (0, import_db9.signFederatedRequest)({
6227
+ const headers = await (0, import_db10.signFederatedRequest)({
5809
6228
  secret,
5810
6229
  sender: ctx.chapter.id,
5811
6230
  method: "PATCH",
@@ -5837,7 +6256,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
5837
6256
  if (req.method === "PATCH" && Number(req.headers.get("content-length") ?? 0) > MAX_BODY) {
5838
6257
  return json({ error: "mutation body is too large", code: "body_too_large" }, 413);
5839
6258
  }
5840
- const got = await gate6(req, env, ctx);
6259
+ const got = await gate7(req, env, ctx);
5841
6260
  if ("response" in got) return got.response;
5842
6261
  if (req.method === "PATCH") {
5843
6262
  let input;
@@ -5850,10 +6269,10 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
5850
6269
  } catch {
5851
6270
  return json({ error: "invalid JSON body", code: "invalid_json" }, 422);
5852
6271
  }
5853
- const target2 = targetFor(ctx, input?.targetId);
6272
+ const target2 = targetFor2(ctx, input?.targetId);
5854
6273
  const hasFields = Boolean(input?.fields && typeof input.fields === "object" && !Array.isArray(input.fields));
5855
6274
  const hasStage = typeof input?.stage === "string";
5856
- if (!target2 || !IDENTIFIER.test(input.type) || !RECORD_ID.test(input.recordId) || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || !MUTATION_ID.test(input.mutationId) || hasFields === hasStage) {
6275
+ if (!target2 || !IDENTIFIER.test(input.type) || !RECORD_ID.test(input.recordId) || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || !MUTATION_ID2.test(input.mutationId) || hasFields === hasStage) {
5857
6276
  return json({ error: "invalid network mutation", code: "invalid_mutation" }, 422);
5858
6277
  }
5859
6278
  return mutateTargetRecord(got.db, env, ctx, target2, {
@@ -5868,7 +6287,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
5868
6287
  }
5869
6288
  const targetId = url.searchParams.get("targetId") ?? "";
5870
6289
  const type = url.searchParams.get("type") ?? "";
5871
- const target = targetFor(ctx, targetId);
6290
+ const target = targetFor2(ctx, targetId);
5872
6291
  if (!target || !IDENTIFIER.test(type)) return json({ error: "configured targetId and record type are required" }, 400);
5873
6292
  const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
5874
6293
  const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
@@ -5885,8 +6304,8 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
5885
6304
  // src/worker-routes-network-notes.ts
5886
6305
  var IDENTIFIER2 = /^[a-z][a-zA-Z0-9_]*$/;
5887
6306
  var RECORD_ID2 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
5888
- var MAX_NOTE2 = 4e3;
5889
- async function gate7(req, env, ctx) {
6307
+ var MAX_NOTE3 = 4e3;
6308
+ async function gate8(req, env, ctx) {
5890
6309
  const db = ctx.makeDb(env);
5891
6310
  const user = await ctx.verifyUser(req, env);
5892
6311
  if (!user) return { response: json({ error: "unauthorized" }, 401) };
@@ -5920,7 +6339,7 @@ function asNote(row) {
5920
6339
  }
5921
6340
  var handleAdminNetworkNotes = async (req, url, env, ctx) => {
5922
6341
  if (url.pathname !== "/api/admin/network/notes" || req.method !== "GET" && req.method !== "POST") return null;
5923
- const got = await gate7(req, env, ctx);
6342
+ const got = await gate8(req, env, ctx);
5924
6343
  if ("response" in got) return got.response;
5925
6344
  const identity = noteIdentity(url, ctx);
5926
6345
  if (!identity) return json({ error: "valid targetId, type, and recordId are required" }, 400);
@@ -5937,7 +6356,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
5937
6356
  return json({ notes: (result.networkNotes ?? []).map((row) => asNote(row)) });
5938
6357
  }
5939
6358
  const contentLength = Number(req.headers.get("content-length") ?? 0);
5940
- if (contentLength > MAX_NOTE2 * 2) return json({ error: "note is too large" }, 413);
6359
+ if (contentLength > MAX_NOTE3 * 2) return json({ error: "note is too large" }, 413);
5941
6360
  let input;
5942
6361
  try {
5943
6362
  input = await req.json();
@@ -5945,7 +6364,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
5945
6364
  return json({ error: "invalid JSON body" }, 400);
5946
6365
  }
5947
6366
  const body = typeof input.body === "string" ? input.body.trim() : "";
5948
- if (!body || body.length > MAX_NOTE2) return json({ error: `note must be 1\u2013${MAX_NOTE2} characters` }, 400);
6367
+ if (!body || body.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
5949
6368
  const id = crypto.randomUUID();
5950
6369
  const note = {
5951
6370
  id,
@@ -5967,14 +6386,14 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
5967
6386
  };
5968
6387
 
5969
6388
  // src/worker-routes-network-shared-notes.ts
5970
- var import_crm8 = require("@odla-ai/crm");
5971
- var import_db10 = require("@odla-ai/db");
6389
+ var import_crm10 = require("@odla-ai/crm");
6390
+ var import_db11 = require("@odla-ai/db");
5972
6391
  var IDENTIFIER3 = /^[a-z][a-zA-Z0-9_]*$/;
5973
6392
  var RECORD_ID3 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
5974
- var MUTATION_ID2 = /^[A-Za-z0-9_-]{16,128}$/;
5975
- var MAX_NOTE3 = 4e3;
6393
+ var MUTATION_ID3 = /^[A-Za-z0-9_-]{16,128}$/;
6394
+ var MAX_NOTE4 = 4e3;
5976
6395
  var NETWORK_META = "chapterNetwork";
5977
- async function gate8(req, env, ctx) {
6396
+ async function gate9(req, env, ctx) {
5978
6397
  const db = ctx.makeDb(env);
5979
6398
  const user = await ctx.verifyUser(req, env);
5980
6399
  if (!user) return { response: json({ error: "unauthorized" }, 401) };
@@ -5997,7 +6416,7 @@ function sharedNote(activity, sourceId) {
5997
6416
  }
5998
6417
  async function listSharedNotes(db, sourceId, recordId) {
5999
6418
  const result = await db.query({
6000
- [import_crm8.CRM_NS.activity]: {
6419
+ [import_crm10.CRM_NS.activity]: {
6001
6420
  $: {
6002
6421
  where: {
6003
6422
  and: [
@@ -6010,7 +6429,7 @@ async function listSharedNotes(db, sourceId, recordId) {
6010
6429
  }
6011
6430
  }
6012
6431
  });
6013
- return (result[import_crm8.CRM_NS.activity] ?? []).flatMap((activity) => {
6432
+ return (result[import_crm10.CRM_NS.activity] ?? []).flatMap((activity) => {
6014
6433
  const note = sharedNote(activity, sourceId);
6015
6434
  return note ? [note] : [];
6016
6435
  });
@@ -6020,14 +6439,14 @@ function sourceLabel(sourceId) {
6020
6439
  }
6021
6440
  var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6022
6441
  if (url.pathname !== "/api/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
6023
- if (req.method === "POST" && Number(req.headers.get("content-length") ?? 0) > MAX_NOTE3 * 2) {
6442
+ if (req.method === "POST" && Number(req.headers.get("content-length") ?? 0) > MAX_NOTE4 * 2) {
6024
6443
  return json({ error: "note is too large" }, 413);
6025
6444
  }
6026
6445
  const db = ctx.makeDb(env);
6027
6446
  const secret = await getVaultSecret(db, "network_share_secret");
6028
6447
  const readers = ctx.chapter.network.readers;
6029
6448
  if (!secret || readers.length === 0) return json({ error: "unauthorized" }, 401);
6030
- const verified = await (0, import_db10.verifyFederatedRequest)(req, {
6449
+ const verified = await (0, import_db11.verifyFederatedRequest)(req, {
6031
6450
  secret,
6032
6451
  senders: readers.map((reader2) => reader2.id)
6033
6452
  });
@@ -6038,7 +6457,7 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6038
6457
  if (!IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId) || !reader?.sharedNotes.includes(recordType)) {
6039
6458
  return json({ error: "shared notes are not enabled for this reader and record type" }, 403);
6040
6459
  }
6041
- const record4 = await (0, import_crm8.getRecord)({ crm: ctx.chapter.crm, db }, recordId);
6460
+ const record4 = await (0, import_crm10.getRecord)({ crm: ctx.chapter.crm, db }, recordId);
6042
6461
  if (!record4 || record4.type !== recordType) return json({ error: "record not found" }, 404);
6043
6462
  if (req.method === "GET") {
6044
6463
  return json({
@@ -6055,12 +6474,12 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6055
6474
  }
6056
6475
  const body = typeof input.body === "string" ? input.body.trim() : "";
6057
6476
  const mutationId = typeof input.mutationId === "string" ? input.mutationId : "";
6058
- if (!body || body.length > MAX_NOTE3 || !MUTATION_ID2.test(mutationId)) {
6059
- return json({ error: `body must be 1\u2013${MAX_NOTE3} characters and mutationId must be valid` }, 400);
6477
+ if (!body || body.length > MAX_NOTE4 || !MUTATION_ID3.test(mutationId)) {
6478
+ return json({ error: `body must be 1\u2013${MAX_NOTE4} characters and mutationId must be valid` }, 400);
6060
6479
  }
6061
6480
  const createdAt = Date.now();
6062
6481
  const id = `network:${verified.sender}:${mutationId}`;
6063
- const result = await (0, import_crm8.addActivity)(
6482
+ const result = await (0, import_crm10.addActivity)(
6064
6483
  {
6065
6484
  crm: ctx.chapter.crm,
6066
6485
  db,
@@ -6093,17 +6512,17 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6093
6512
  };
6094
6513
  return json({ version: 1, site: { id: ctx.chapter.id, name: ctx.chapter.name }, note, duplicate: result.duplicate }, 201);
6095
6514
  };
6096
- function targetFor2(ctx, id) {
6515
+ function targetFor3(ctx, id) {
6097
6516
  return ctx.chapter.network.targets.find((target) => target.id === id);
6098
6517
  }
6099
6518
  var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
6100
6519
  if (url.pathname !== "/api/admin/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
6101
- const got = await gate8(req, env, ctx);
6520
+ const got = await gate9(req, env, ctx);
6102
6521
  if ("response" in got) return got.response;
6103
6522
  const targetId = url.searchParams.get("targetId") ?? "";
6104
6523
  const recordType = url.searchParams.get("type") ?? "";
6105
6524
  const recordId = url.searchParams.get("recordId") ?? "";
6106
- const target = targetFor2(ctx, targetId);
6525
+ const target = targetFor3(ctx, targetId);
6107
6526
  if (!target || !target.sharedNotes.includes(recordType) || !IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId)) {
6108
6527
  return json({ error: "configured targetId, type, and recordId are required" }, 400);
6109
6528
  }
@@ -6116,7 +6535,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
6116
6535
  return json({ error: "invalid JSON body" }, 400);
6117
6536
  }
6118
6537
  const note = typeof input.body === "string" ? input.body.trim() : "";
6119
- if (!note || note.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
6538
+ if (!note || note.length > MAX_NOTE4) return json({ error: `note must be 1\u2013${MAX_NOTE4} characters` }, 400);
6120
6539
  body = JSON.stringify({ body: note, mutationId: crypto.randomUUID() });
6121
6540
  }
6122
6541
  const secret = await getVaultSecret(got.db, target.secretName);
@@ -6124,7 +6543,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
6124
6543
  const destination = new URL("/api/network/shared-notes", target.url);
6125
6544
  destination.search = new URLSearchParams({ type: recordType, recordId }).toString();
6126
6545
  try {
6127
- const headers = await (0, import_db10.signFederatedRequest)({
6546
+ const headers = await (0, import_db11.signFederatedRequest)({
6128
6547
  secret,
6129
6548
  sender: ctx.chapter.id,
6130
6549
  method: req.method,
@@ -6151,7 +6570,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
6151
6570
  };
6152
6571
 
6153
6572
  // src/worker-routes-formation.ts
6154
- var import_crm9 = require("@odla-ai/crm");
6573
+ var import_crm11 = require("@odla-ai/crm");
6155
6574
  var SUBMISSION = /^[A-Za-z0-9_-]{8,128}$/;
6156
6575
  var handleFormation = async (req, url, env, ctx) => {
6157
6576
  const formation = ctx.chapter.formation;
@@ -6203,7 +6622,7 @@ var handleFormation = async (req, url, env, ctx) => {
6203
6622
  const db = ctx.makeDb(env);
6204
6623
  const id = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
6205
6624
  try {
6206
- const created = await (0, import_crm9.createRecord)(
6625
+ const created = await (0, import_crm11.createRecord)(
6207
6626
  {
6208
6627
  crm: ctx.chapter.crm,
6209
6628
  db,
@@ -6231,7 +6650,7 @@ var handleFormation = async (req, url, env, ctx) => {
6231
6650
  };
6232
6651
 
6233
6652
  // src/worker-routes-builder-library.ts
6234
- var import_db11 = require("@odla-ai/db");
6653
+ var import_db12 = require("@odla-ai/db");
6235
6654
 
6236
6655
  // src/builder-library-validation.ts
6237
6656
  var ID4 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
@@ -6514,7 +6933,7 @@ async function verifySource(req, db, ctx) {
6514
6933
  if (ctx.chapter.mode === "chapter") {
6515
6934
  const secret = await getVaultSecret(db, "network_share_secret");
6516
6935
  if (!secret) return null;
6517
- const verified = await (0, import_db11.verifyFederatedRequest)(req, {
6936
+ const verified = await (0, import_db12.verifyFederatedRequest)(req, {
6518
6937
  secret,
6519
6938
  senders: sources.map((source2) => source2.sourceId)
6520
6939
  });
@@ -6527,7 +6946,7 @@ async function verifySource(req, db, ctx) {
6527
6946
  if (!target) continue;
6528
6947
  const secret = await getVaultSecret(db, target.secretName);
6529
6948
  if (!secret) continue;
6530
- const verified = await (0, import_db11.verifyFederatedRequest)(req.clone(), { secret, senders: [source.sourceId] });
6949
+ const verified = await (0, import_db12.verifyFederatedRequest)(req.clone(), { secret, senders: [source.sourceId] });
6531
6950
  if (verified.ok) return { source, body: verified.body };
6532
6951
  }
6533
6952
  return null;
@@ -6716,11 +7135,11 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
6716
7135
  };
6717
7136
 
6718
7137
  // src/signup-control-delivery.ts
6719
- var import_db12 = require("@odla-ai/db");
7138
+ var import_db13 = require("@odla-ai/db");
6720
7139
  async function deliverSignupControl(options) {
6721
7140
  const destination = new URL(SIGNUP_CONTROL_PATH, options.targetUrl);
6722
7141
  const body = JSON.stringify(options.envelope);
6723
- const signed = await (0, import_db12.signFederatedRequest)({
7142
+ const signed = await (0, import_db13.signFederatedRequest)({
6724
7143
  secret: options.secret,
6725
7144
  sender: options.sender,
6726
7145
  method: "POST",
@@ -6741,11 +7160,11 @@ async function deliverSignupControl(options) {
6741
7160
  }
6742
7161
 
6743
7162
  // src/member-content-delivery.ts
6744
- var import_db13 = require("@odla-ai/db");
7163
+ var import_db14 = require("@odla-ai/db");
6745
7164
  async function deliverMemberContent(options) {
6746
7165
  const destination = new URL(MEMBER_CONTENT_CONTROL_PATH, options.targetUrl);
6747
7166
  const body = JSON.stringify(options.envelope);
6748
- const signed = await (0, import_db13.signFederatedRequest)({
7167
+ const signed = await (0, import_db14.signFederatedRequest)({
6749
7168
  secret: options.secret,
6750
7169
  sender: options.sender,
6751
7170
  method: "POST",
@@ -6766,11 +7185,11 @@ async function deliverMemberContent(options) {
6766
7185
  }
6767
7186
 
6768
7187
  // src/builder-library-delivery.ts
6769
- var import_db14 = require("@odla-ai/db");
7188
+ var import_db15 = require("@odla-ai/db");
6770
7189
  async function deliverBuilderLibrary(options) {
6771
7190
  const destination = new URL(BUILDER_LIBRARY_CONTROL_PATH, options.targetUrl);
6772
7191
  const body = JSON.stringify(options.envelope);
6773
- const signed = await (0, import_db14.signFederatedRequest)({ secret: options.secret, sender: options.sender, method: "POST", url: destination, body });
7192
+ const signed = await (0, import_db15.signFederatedRequest)({ secret: options.secret, sender: options.sender, method: "POST", url: destination, body });
6774
7193
  const response = await (options.fetch ?? globalThis.fetch)(destination, { method: "POST", headers: { ...signed, "content-type": "application/json" }, body });
6775
7194
  const result = await response.json();
6776
7195
  if (!response.ok || !("ok" in result) || result.ok !== true) {
@@ -6780,31 +7199,6 @@ async function deliverBuilderLibrary(options) {
6780
7199
  return result;
6781
7200
  }
6782
7201
 
6783
- // src/admission-grants-delivery.ts
6784
- var import_db15 = require("@odla-ai/db");
6785
- async function deliverAdmissionGrants(options) {
6786
- const destination = new URL(ADMISSION_GRANTS_PATH, options.targetUrl);
6787
- const body = JSON.stringify(options.envelope);
6788
- const signed = await (0, import_db15.signFederatedRequest)({
6789
- secret: options.secret,
6790
- sender: options.sender,
6791
- method: "POST",
6792
- url: destination,
6793
- body
6794
- });
6795
- const response = await (options.fetch ?? globalThis.fetch)(destination, {
6796
- method: "POST",
6797
- headers: { ...signed, "content-type": "application/json" },
6798
- body
6799
- });
6800
- const result = await response.json();
6801
- if (!response.ok || !("ok" in result) || result.ok !== true) {
6802
- const failure = result;
6803
- throw new Error(`admission grants rejected (${response.status}): ${failure.reason ?? failure.error ?? "unknown"}`);
6804
- }
6805
- return result;
6806
- }
6807
-
6808
7202
  // src/signup-control-theme.ts
6809
7203
  var SIGNUP_CONTROL_THEME_MARKER = "<!--odla:signup-control-theme-->";
6810
7204
  var SIGNUP_CONTROL_THEME_STYLE_ID = "odla-signup-control-theme";
@@ -6964,6 +7358,7 @@ var BUILTIN_ROUTES = [
6964
7358
  handleAdminPeopleRole,
6965
7359
  handleAdminCrmSync,
6966
7360
  handleAdminAdmissionGrants,
7361
+ handleAdminNetworkAdmissions,
6967
7362
  handleAdminClerkPrivateProfileSync,
6968
7363
  // Aggregation
6969
7364
  handleAdminDashboard,