@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.
@@ -607,6 +607,11 @@ function validateAdmissionGrantDocument(input) {
607
607
  }
608
608
  };
609
609
  }
610
+ async function createAdmissionGrantEnvelope(document) {
611
+ const checked = validateAdmissionGrantDocument(document);
612
+ if (!checked.ok) throw new TypeError(`Invalid admission grant document: ${checked.errors.join("; ")}`);
613
+ return { document: checked.value, digest: await signupControlDigest(checked.value) };
614
+ }
610
615
  var rows = (value) => Array.isArray(value) ? value : [];
611
616
  function asGrant(row) {
612
617
  if (typeof row.id !== "string" || typeof row.emailNormalized !== "string") return null;
@@ -3110,12 +3115,17 @@ import { verifyFederatedRequest as verifyFederatedRequest4 } from "@odla-ai/db";
3110
3115
  var reject3 = (reason, status, errors) => json({ ok: false, reason, ...errors ? { errors } : {} }, status);
3111
3116
  var handleAdmissionGrants = async (req, url, env, ctx) => {
3112
3117
  if (req.method !== "POST" || url.pathname !== ADMISSION_GRANTS_PATH) return null;
3113
- const authority = ctx.chapter.signupControl;
3114
- if (!authority) return json({ error: "not found" }, 404);
3118
+ const claimedSender = req.headers.get("x-odla-federation-sender") ?? "";
3119
+ const networkReader = ctx.chapter.network.readers.find((reader) => reader.id === claimedSender && reader.admissionGrants);
3120
+ const signupAuthority = ctx.chapter.signupControl?.sourceId === claimedSender ? ctx.chapter.signupControl : void 0;
3121
+ if (!networkReader && !signupAuthority) return json({ error: "not found" }, 404);
3115
3122
  const db = ctx.makeDb(env);
3116
- const secret = await getVaultSecret(db, authority.secretName);
3123
+ const secret = await getVaultSecret(
3124
+ db,
3125
+ networkReader ? "network_share_secret" : signupAuthority.secretName
3126
+ );
3117
3127
  if (!secret) return json({ error: "unauthorized" }, 401);
3118
- const verified = await verifyFederatedRequest4(req, { secret, senders: [authority.sourceId] });
3128
+ const verified = await verifyFederatedRequest4(req, { secret, senders: [claimedSender] });
3119
3129
  if (!verified.ok) return json({ error: "unauthorized", reason: verified.reason }, 401);
3120
3130
  let input;
3121
3131
  try {
@@ -3127,11 +3137,11 @@ var handleAdmissionGrants = async (req, url, env, ctx) => {
3127
3137
  const checked = validateAdmissionGrantDocument(input.document);
3128
3138
  if (!checked.ok) return reject3("invalid_document", 400, checked.errors);
3129
3139
  const document = checked.value;
3130
- if (document.source.appId !== authority.sourceId) return reject3("wrong_source", 403);
3140
+ if (document.source.appId !== claimedSender) return reject3("wrong_source", 403);
3131
3141
  if (document.target.chapterId !== ctx.chapter.id) return reject3("wrong_target", 409);
3132
3142
  if (document.target.environment !== env.ODLA_ENV) return reject3("wrong_environment", 409);
3133
3143
  if (!digest || await signupControlDigest(document) !== digest) return reject3("invalid_digest", 400);
3134
- const latest = await loadLatestAdmissionRevision(db, authority.sourceId, ctx.chapter.id, env.ODLA_ENV);
3144
+ const latest = await loadLatestAdmissionRevision(db, claimedSender, ctx.chapter.id, env.ODLA_ENV);
3135
3145
  if (latest && document.revision < latest.revision) return reject3("stale_revision", 409);
3136
3146
  if (latest && document.revision === latest.revision) {
3137
3147
  if (latest.digest !== digest) return reject3("conflicting_revision", 409);
@@ -3256,6 +3266,384 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
3256
3266
  return null;
3257
3267
  };
3258
3268
 
3269
+ // src/worker-routes-network-admission.ts
3270
+ import { getRecord as getRecord2 } from "@odla-ai/crm";
3271
+
3272
+ // src/admission-grants-delivery.ts
3273
+ import { signFederatedRequest } from "@odla-ai/db";
3274
+ var AdmissionGrantDeliveryError = class extends Error {
3275
+ constructor(status, reason) {
3276
+ super(`admission grants rejected (${status}): ${reason}`);
3277
+ this.status = status;
3278
+ this.reason = reason;
3279
+ this.name = "AdmissionGrantDeliveryError";
3280
+ }
3281
+ status;
3282
+ reason;
3283
+ };
3284
+ async function deliverAdmissionGrants(options) {
3285
+ const destination = new URL(ADMISSION_GRANTS_PATH, options.targetUrl);
3286
+ const body = JSON.stringify(options.envelope);
3287
+ const signed = await signFederatedRequest({
3288
+ secret: options.secret,
3289
+ sender: options.sender,
3290
+ method: "POST",
3291
+ url: destination,
3292
+ body
3293
+ });
3294
+ const response = await (options.fetch ?? globalThis.fetch)(destination, {
3295
+ method: "POST",
3296
+ headers: { ...signed, "content-type": "application/json" },
3297
+ body
3298
+ });
3299
+ const result = await response.json();
3300
+ if (!response.ok || !("ok" in result) || result.ok !== true) {
3301
+ const failure = result;
3302
+ throw new AdmissionGrantDeliveryError(
3303
+ response.status,
3304
+ failure.reason ?? failure.error ?? "unknown"
3305
+ );
3306
+ }
3307
+ return result;
3308
+ }
3309
+
3310
+ // src/worker-network-fetch.ts
3311
+ var NetworkTargetTransportError = class extends Error {
3312
+ code = "binding_unavailable";
3313
+ binding;
3314
+ constructor(binding) {
3315
+ super(`service binding "${binding}" is unavailable`);
3316
+ this.name = "NetworkTargetTransportError";
3317
+ this.binding = binding;
3318
+ }
3319
+ };
3320
+ function resolveNetworkTargetTransport(env, target) {
3321
+ if (!target.binding) {
3322
+ return {
3323
+ kind: "url",
3324
+ fetch: (destination, init) => globalThis.fetch(destination, init)
3325
+ };
3326
+ }
3327
+ const candidate = env[target.binding];
3328
+ if (typeof candidate !== "object" || candidate === null || !("fetch" in candidate) || typeof candidate.fetch !== "function") {
3329
+ throw new NetworkTargetTransportError(target.binding);
3330
+ }
3331
+ const binding = candidate;
3332
+ return {
3333
+ kind: "binding",
3334
+ binding: target.binding,
3335
+ // Keep the receiver attached: Cloudflare service bindings require it.
3336
+ fetch: (destination, init) => binding.fetch(new Request(destination, init))
3337
+ };
3338
+ }
3339
+ function fetchNetworkTarget(env, target, destination, init) {
3340
+ return resolveNetworkTargetTransport(env, target).fetch(destination, init);
3341
+ }
3342
+
3343
+ // src/worker-network-admission-state.ts
3344
+ import { getRecord } from "@odla-ai/crm";
3345
+ var MAX_ASSIGNMENTS = 2e3;
3346
+ var admissionRows = (value) => Array.isArray(value) ? value : [];
3347
+ function assignmentId(targetId, personId) {
3348
+ return `leader-admission:${targetId}:${personId}`;
3349
+ }
3350
+ function deliveryId(targetId, environment) {
3351
+ return `leader-admission-delivery:${targetId}:${environment}`;
3352
+ }
3353
+ async function loadLeaderAdmissionAssignments(db, targetId) {
3354
+ const result = await db.query({ leaderAdmissionAssignments: { $: {
3355
+ where: { targetId },
3356
+ order: { updatedAt: "desc" },
3357
+ limit: MAX_ASSIGNMENTS
3358
+ } } });
3359
+ return admissionRows(result.leaderAdmissionAssignments);
3360
+ }
3361
+ async function loadLeaderAdmissionDelivery(db, targetId, environment) {
3362
+ const id = deliveryId(targetId, environment);
3363
+ const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id }, limit: 1 } } });
3364
+ return admissionRows(result.leaderAdmissionDeliveries)[0] ?? null;
3365
+ }
3366
+ function personAdmissionEmail(ctx, record4) {
3367
+ if (record4.type !== "person") return null;
3368
+ const field = ctx.chapter.crm.config.types.person?.emailField;
3369
+ if (!field) return null;
3370
+ const raw = record4.fields[field] ?? record4.primaryEmail;
3371
+ if (typeof raw !== "string") return null;
3372
+ const normalized = normalizeAdmissionEmail(raw);
3373
+ return isValidEmail(normalized) ? normalized : null;
3374
+ }
3375
+ async function grantInput(db, ctx, assignment) {
3376
+ const person = await getRecord({ crm: ctx.chapter.crm, db }, assignment.personId);
3377
+ const email = person ? personAdmissionEmail(ctx, person) : null;
3378
+ if (!email) throw new Error(`person ${assignment.personId} has no valid authoritative email`);
3379
+ return {
3380
+ id: assignment.grantId,
3381
+ email,
3382
+ decision: assignment.decision,
3383
+ ...assignment.note ? { note: assignment.note } : {},
3384
+ ...assignment.expiresAt !== void 0 ? { expiresAt: assignment.expiresAt } : {}
3385
+ };
3386
+ }
3387
+ async function persistLeaderAdmissionDesiredSet(opts) {
3388
+ const now = Date.now();
3389
+ const current = await loadLeaderAdmissionAssignments(opts.db, opts.target.id);
3390
+ const id = assignmentId(opts.target.id, opts.personId);
3391
+ const existing = current.find((row) => row.id === id);
3392
+ const digest = await signupControlDigest({
3393
+ targetId: opts.target.id,
3394
+ personId: opts.personId,
3395
+ decision: opts.decision,
3396
+ note: opts.note ?? null,
3397
+ expiresAt: opts.expiresAt ?? null,
3398
+ active: opts.active
3399
+ });
3400
+ const mutationReceiptId = `leader-admission-mutation:${opts.target.id}:${opts.mutationId}`;
3401
+ const priorMutation2 = await opts.db.query({ leaderAdmissionMutations: { $: {
3402
+ where: { id: mutationReceiptId },
3403
+ limit: 1
3404
+ } } });
3405
+ const receipt = admissionRows(priorMutation2.leaderAdmissionMutations)[0];
3406
+ if (receipt) {
3407
+ if (receipt.requestDigest !== digest || receipt.personId !== opts.personId) {
3408
+ throw new Error("mutation_conflict");
3409
+ }
3410
+ const saved = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
3411
+ if (!saved) throw new Error("delivery_missing");
3412
+ return { delivery: saved, duplicate: true };
3413
+ }
3414
+ if (existing?.lastMutationId === opts.mutationId) {
3415
+ if (existing.lastRequestDigest !== digest) throw new Error("mutation_conflict");
3416
+ const saved = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
3417
+ if (!saved) throw new Error("delivery_missing");
3418
+ return { delivery: saved, duplicate: true };
3419
+ }
3420
+ const assignment = {
3421
+ id,
3422
+ targetId: opts.target.id,
3423
+ personId: opts.personId,
3424
+ grantId: existing?.grantId ?? crypto.randomUUID().replaceAll("-", ""),
3425
+ decision: opts.decision,
3426
+ ...opts.note ? { note: opts.note } : {},
3427
+ ...opts.expiresAt !== void 0 ? { expiresAt: opts.expiresAt } : {},
3428
+ active: opts.active,
3429
+ lastMutationId: opts.mutationId,
3430
+ lastRequestDigest: digest,
3431
+ createdAt: existing?.createdAt ?? now,
3432
+ createdBy: existing?.createdBy ?? opts.actor,
3433
+ updatedAt: now,
3434
+ updatedBy: opts.actor
3435
+ };
3436
+ const desired = current.filter((row) => row.id !== id && row.active).concat(assignment.active ? [assignment] : []);
3437
+ const grants = await Promise.all(desired.map((row) => grantInput(opts.db, opts.ctx, row)));
3438
+ const previous = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
3439
+ const revision = (previous?.revision ?? 0) + 1;
3440
+ const envelope = await createAdmissionGrantEnvelope({
3441
+ schemaVersion: 1,
3442
+ revision,
3443
+ generatedAt: now,
3444
+ source: { appId: opts.ctx.chapter.id, actor: opts.actor },
3445
+ target: { chapterId: opts.target.id, environment: opts.env.ODLA_ENV },
3446
+ grants
3447
+ });
3448
+ const delivery = {
3449
+ id: deliveryId(opts.target.id, opts.env.ODLA_ENV),
3450
+ targetId: opts.target.id,
3451
+ environment: opts.env.ODLA_ENV,
3452
+ revision,
3453
+ digest: envelope.digest,
3454
+ envelope,
3455
+ status: "saved",
3456
+ attempts: 0,
3457
+ updatedAt: now
3458
+ };
3459
+ const ops = [
3460
+ { t: "update", ns: "leaderAdmissionAssignments", id, attrs: assignment },
3461
+ { t: "update", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: delivery },
3462
+ { t: "update", ns: "leaderAdmissionMutations", id: mutationReceiptId, attrs: {
3463
+ id: mutationReceiptId,
3464
+ targetId: opts.target.id,
3465
+ personId: opts.personId,
3466
+ requestDigest: digest,
3467
+ revision,
3468
+ createdAt: now
3469
+ } }
3470
+ ];
3471
+ const retract = [
3472
+ ...!opts.note && existing?.note !== void 0 ? ["note"] : [],
3473
+ ...opts.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
3474
+ ];
3475
+ if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id, attrs: retract });
3476
+ const tx = await opts.db.transact(ops, {
3477
+ mutationId: `leader-admission:${opts.target.id}:${opts.mutationId}`,
3478
+ guards: previous ? [{ ns: "leaderAdmissionDeliveries", id: previous.id, exists: true, equals: { revision: previous.revision } }] : [{ ns: "leaderAdmissionDeliveries", id: delivery.id, exists: false }]
3479
+ });
3480
+ return { delivery, duplicate: tx.duplicate };
3481
+ }
3482
+
3483
+ // src/worker-routes-network-admission.ts
3484
+ var PATH = "/api/admin/network/admissions";
3485
+ var RETRY_PATH = "/api/admin/network/admissions/retry";
3486
+ var REVOKE_PATH = "/api/admin/network/admissions/revoke";
3487
+ var MUTATION_ID = /^[A-Za-z0-9_-]{16,128}$/;
3488
+ var MAX_NOTE2 = 2e3;
3489
+ var reject4 = (status, code, error) => json({ ok: false, code, error }, status);
3490
+ async function gate2(req, env, ctx) {
3491
+ const db = ctx.makeDb(env);
3492
+ const user = await ctx.verifyUser(req, env);
3493
+ if (!user) return { response: reject4(401, "unauthorized", "unauthorized") };
3494
+ if (!await ctx.isAdmin(db, user)) return { response: reject4(403, "forbidden", "forbidden") };
3495
+ return { db, user };
3496
+ }
3497
+ function targetFor(ctx, id) {
3498
+ return ctx.chapter.network.targets.find((target) => target.id === id && target.admissionGrants);
3499
+ }
3500
+ async function deliverSaved(db, env, ctx, target, delivery) {
3501
+ if (delivery.status === "acknowledged") return delivery;
3502
+ const secret = await getVaultSecret(db, target.secretName);
3503
+ const attemptedAt = Date.now();
3504
+ const attempts = delivery.attempts + 1;
3505
+ let patch;
3506
+ if (!secret) {
3507
+ patch = {
3508
+ status: delivery.attempts > 0 ? "retrying" : "pending",
3509
+ attempts,
3510
+ lastAttemptAt: attemptedAt,
3511
+ lastError: "edge secret is missing",
3512
+ updatedAt: attemptedAt
3513
+ };
3514
+ } else {
3515
+ try {
3516
+ const receipt = await deliverAdmissionGrants({
3517
+ secret,
3518
+ sender: ctx.chapter.id,
3519
+ targetUrl: target.url,
3520
+ envelope: delivery.envelope,
3521
+ fetch: ((input, init) => fetchNetworkTarget(env, target, new URL(String(input)), init ?? {}))
3522
+ });
3523
+ patch = {
3524
+ status: "acknowledged",
3525
+ attempts,
3526
+ lastAttemptAt: attemptedAt,
3527
+ acknowledgedAt: receipt.acknowledgedAt,
3528
+ updatedAt: attemptedAt
3529
+ };
3530
+ } catch (error) {
3531
+ const permanent = error instanceof AdmissionGrantDeliveryError && error.status >= 400 && error.status < 500;
3532
+ patch = {
3533
+ status: permanent ? "rejected" : delivery.attempts > 0 ? "retrying" : "pending",
3534
+ attempts,
3535
+ lastAttemptAt: attemptedAt,
3536
+ lastError: error instanceof Error ? error.message.slice(0, 2e3) : "delivery failed",
3537
+ updatedAt: attemptedAt
3538
+ };
3539
+ }
3540
+ }
3541
+ const ops = [{
3542
+ t: "update",
3543
+ ns: "leaderAdmissionDeliveries",
3544
+ id: delivery.id,
3545
+ attrs: patch
3546
+ }];
3547
+ if (patch.status === "acknowledged" && delivery.lastError) {
3548
+ ops.push({ t: "retract", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: ["lastError"] });
3549
+ }
3550
+ await db.transact(ops, {
3551
+ mutationId: `leader-admission-attempt:${delivery.targetId}:${delivery.revision}:${attempts}:${patch.status}`
3552
+ });
3553
+ return { ...delivery, ...patch };
3554
+ }
3555
+ async function parseBody(req) {
3556
+ try {
3557
+ return await req.json();
3558
+ } catch {
3559
+ return null;
3560
+ }
3561
+ }
3562
+ var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
3563
+ if (![PATH, RETRY_PATH, REVOKE_PATH].includes(url.pathname)) return null;
3564
+ const auth = await gate2(req, env, ctx);
3565
+ if ("response" in auth) return auth.response;
3566
+ const db = auth.db;
3567
+ if (url.pathname === PATH && req.method === "GET") {
3568
+ const targetId2 = url.searchParams.get("targetId") ?? "";
3569
+ const target2 = targetFor(ctx, targetId2);
3570
+ if (!target2) return reject4(404, "unknown_target", "admission target is not configured");
3571
+ return json({
3572
+ ok: true,
3573
+ target: { id: target2.id, name: target2.name },
3574
+ assignments: await loadLeaderAdmissionAssignments(db, target2.id),
3575
+ delivery: await loadLeaderAdmissionDelivery(db, target2.id, env.ODLA_ENV)
3576
+ });
3577
+ }
3578
+ if (req.method !== "POST") return null;
3579
+ const body = await parseBody(req);
3580
+ if (!body) return reject4(422, "invalid_request", "request body must be JSON");
3581
+ const targetId = typeof body.targetId === "string" ? body.targetId : "";
3582
+ const target = targetFor(ctx, targetId);
3583
+ if (!target) return reject4(404, "unknown_target", "admission target is not configured");
3584
+ if (url.pathname === RETRY_PATH) {
3585
+ const delivery = await loadLeaderAdmissionDelivery(db, target.id, env.ODLA_ENV);
3586
+ if (!delivery) return reject4(404, "not_found", "no saved admission delivery exists");
3587
+ return json({ ok: true, delivery: await deliverSaved(db, env, ctx, target, delivery) });
3588
+ }
3589
+ const personId = typeof body.personId === "string" ? body.personId : "";
3590
+ const mutationId = typeof body.mutationId === "string" ? body.mutationId : "";
3591
+ if (!personId || !MUTATION_ID.test(mutationId)) {
3592
+ return reject4(422, "invalid_request", "personId and a stable mutationId are required");
3593
+ }
3594
+ const person = await getRecord2({ crm: ctx.chapter.crm, db: auth.db }, personId);
3595
+ if (!person) return reject4(404, "person_not_found", "person not found");
3596
+ if (!personAdmissionEmail(ctx, person)) {
3597
+ return reject4(422, "invalid_email", "person has no valid authoritative email");
3598
+ }
3599
+ const decision = body.decision ?? "waive_interview";
3600
+ if (decision !== "waive_interview" && decision !== "require_interview") {
3601
+ return reject4(422, "invalid_decision", "decision must be waive_interview or require_interview");
3602
+ }
3603
+ const note = typeof body.note === "string" ? body.note.trim() : "";
3604
+ if (note.length > MAX_NOTE2) return reject4(422, "invalid_note", "note is too long");
3605
+ const expiresAt = body.expiresAt == null ? void 0 : Number(body.expiresAt);
3606
+ if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= Date.now())) {
3607
+ return reject4(422, "invalid_expiry", "expiresAt must be a future epoch timestamp");
3608
+ }
3609
+ try {
3610
+ const saved = await persistLeaderAdmissionDesiredSet({
3611
+ db,
3612
+ ctx,
3613
+ env,
3614
+ target,
3615
+ personId,
3616
+ decision,
3617
+ ...note ? { note } : {},
3618
+ ...expiresAt !== void 0 ? { expiresAt } : {},
3619
+ active: url.pathname !== REVOKE_PATH,
3620
+ mutationId,
3621
+ actor: auth.user.email ?? auth.user.userId
3622
+ });
3623
+ const delivery = await deliverSaved(db, env, ctx, target, saved.delivery);
3624
+ return json({ ok: true, duplicate: saved.duplicate, delivery }, delivery.status === "acknowledged" ? 200 : 202);
3625
+ } catch (error) {
3626
+ if (error instanceof Error && error.message === "mutation_conflict") {
3627
+ return reject4(409, "mutation_conflict", "mutationId was already used for another request");
3628
+ }
3629
+ if (error instanceof Error && /valid authoritative email/.test(error.message)) {
3630
+ return reject4(422, "invalid_email", error.message);
3631
+ }
3632
+ return reject4(409, "revision_conflict", "admission assignments changed; reload and retry");
3633
+ }
3634
+ };
3635
+ async function retryLeaderAdmissionDeliveries(env, ctx, limit = 100) {
3636
+ const db = ctx.makeDb(env);
3637
+ const result = await db.query({ leaderAdmissionDeliveries: { $: { limit } } });
3638
+ const pending = admissionRows(result.leaderAdmissionDeliveries).filter((delivery) => delivery.status !== "acknowledged" && delivery.status !== "rejected").slice(0, Math.max(1, Math.min(limit, 500)));
3639
+ const delivered = [];
3640
+ for (const item of pending) {
3641
+ const target = targetFor(ctx, item.targetId);
3642
+ if (target) delivered.push(await deliverSaved(db, env, ctx, target, item));
3643
+ }
3644
+ return delivered;
3645
+ }
3646
+
3259
3647
  // src/worker-routes-schedule.ts
3260
3648
  import { computeBookableSlots, initCalendar } from "@odla-ai/calendar";
3261
3649
  function errCode(err) {
@@ -4517,16 +4905,16 @@ var crmDeps2 = (db, ctx) => ({
4517
4905
  });
4518
4906
  var handleAdminCrmSync = async (req, url, env, ctx) => {
4519
4907
  if (req.method !== "POST" || url.pathname !== "/api/admin/crm/sync") return null;
4520
- const gate9 = await adminGate(req, env, ctx);
4521
- if (gate9 instanceof Response) return gate9;
4522
- const result = await backfillCrm(crmDeps2(gate9.db, ctx));
4908
+ const gate10 = await adminGate(req, env, ctx);
4909
+ if (gate10 instanceof Response) return gate10;
4910
+ const result = await backfillCrm(crmDeps2(gate10.db, ctx));
4523
4911
  return json({ ok: true, ...result });
4524
4912
  };
4525
4913
  var handleAdminPeople = async (req, url, env, ctx) => {
4526
4914
  if (req.method !== "GET" || url.pathname !== "/api/admin/people") return null;
4527
- const gate9 = await adminGate(req, env, ctx);
4528
- if (gate9 instanceof Response) return gate9;
4529
- const { db } = gate9;
4915
+ const gate10 = await adminGate(req, env, ctx);
4916
+ if (gate10 instanceof Response) return gate10;
4917
+ const { db } = gate10;
4530
4918
  const sk = await getVaultSecret(db, "clerk_secret_key");
4531
4919
  const [data, roleList] = await Promise.all([
4532
4920
  db.query({
@@ -4579,9 +4967,9 @@ var handleAdminPeople = async (req, url, env, ctx) => {
4579
4967
  };
4580
4968
  var handleAdminPeopleAccess = async (req, url, env, ctx) => {
4581
4969
  if (req.method !== "GET" || url.pathname !== "/api/admin/people/access") return null;
4582
- const gate9 = await adminGate(req, env, ctx);
4583
- if (gate9 instanceof Response) return gate9;
4584
- const { db } = gate9;
4970
+ const gate10 = await adminGate(req, env, ctx);
4971
+ if (gate10 instanceof Response) return gate10;
4972
+ const { db } = gate10;
4585
4973
  const targetId = url.searchParams.get("userId") ?? "";
4586
4974
  if (!targetId.startsWith("user_")) return json({ error: "invalid userId" }, 400);
4587
4975
  const sk = await getVaultSecret(db, "clerk_secret_key");
@@ -4592,9 +4980,9 @@ var handleAdminPeopleAccess = async (req, url, env, ctx) => {
4592
4980
  };
4593
4981
  var handleAdminPeopleRole = async (req, url, env, ctx) => {
4594
4982
  if (req.method !== "POST" || url.pathname !== "/api/admin/people/role") return null;
4595
- const gate9 = await adminGate(req, env, ctx);
4596
- if (gate9 instanceof Response) return gate9;
4597
- const { db, actor } = gate9;
4983
+ const gate10 = await adminGate(req, env, ctx);
4984
+ if (gate10 instanceof Response) return gate10;
4985
+ const { db, actor } = gate10;
4598
4986
  let body;
4599
4987
  try {
4600
4988
  body = await req.json();
@@ -4741,7 +5129,7 @@ function subAnnualCents(sub) {
4741
5129
  }
4742
5130
 
4743
5131
  // src/worker-routes-admin-dashboard.ts
4744
- async function gate2(req, env, ctx) {
5132
+ async function gate3(req, env, ctx) {
4745
5133
  const rawDb = ctx.makeDb(env);
4746
5134
  const u = await ctx.verifyUser(req, env);
4747
5135
  if (!u) return json({ error: "unauthorized" }, 401);
@@ -4751,7 +5139,7 @@ async function gate2(req, env, ctx) {
4751
5139
  var rows3 = (v) => Array.isArray(v) ? v : [];
4752
5140
  var handleAdminDashboard = async (req, url, env, ctx) => {
4753
5141
  if (req.method !== "GET" || url.pathname !== "/api/admin/dashboard") return null;
4754
- const got = await gate2(req, env, ctx);
5142
+ const got = await gate3(req, env, ctx);
4755
5143
  if (got instanceof Response) return got;
4756
5144
  const db = got;
4757
5145
  const now = Date.now();
@@ -4834,7 +5222,7 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
4834
5222
  };
4835
5223
  var handleAdminBilling = async (req, url, env, ctx) => {
4836
5224
  if (req.method !== "GET" || url.pathname !== "/api/admin/billing") return null;
4837
- const got = await gate2(req, env, ctx);
5225
+ const got = await gate3(req, env, ctx);
4838
5226
  if (got instanceof Response) return got;
4839
5227
  const db = got;
4840
5228
  const sk = await getVaultSecret(db, "stripe_secret_key");
@@ -4894,7 +5282,7 @@ var handleAdminBilling = async (req, url, env, ctx) => {
4894
5282
 
4895
5283
  // src/worker-routes-admin-lifecycle.ts
4896
5284
  import { computeBookableSlots as computeBookableSlots2, initCalendar as initCalendar3 } from "@odla-ai/calendar";
4897
- async function gate3(req, env, ctx) {
5285
+ async function gate4(req, env, ctx) {
4898
5286
  const rawDb = ctx.makeDb(env);
4899
5287
  const u = await ctx.verifyUser(req, env);
4900
5288
  if (!u) return json({ error: "unauthorized" }, 401);
@@ -4920,7 +5308,7 @@ async function loadGroup(db, id) {
4920
5308
  var handleAdminMeetingReschedule = async (req, url, env, ctx) => {
4921
5309
  const m = url.pathname.match(/^\/api\/admin\/meetings\/([0-9a-f-]+)\/reschedule$/);
4922
5310
  if (req.method !== "POST" || !m) return null;
4923
- const got = await gate3(req, env, ctx);
5311
+ const got = await gate4(req, env, ctx);
4924
5312
  if (got instanceof Response) return got;
4925
5313
  const db = got;
4926
5314
  const body = await readJson(req);
@@ -4961,7 +5349,7 @@ var handleAdminMeetingReschedule = async (req, url, env, ctx) => {
4961
5349
  var handleAdminMeetingCancel = async (req, url, env, ctx) => {
4962
5350
  const m = url.pathname.match(/^\/api\/admin\/meetings\/([0-9a-f-]+)\/cancel$/);
4963
5351
  if (req.method !== "POST" || !m) return null;
4964
- const got = await gate3(req, env, ctx);
5352
+ const got = await gate4(req, env, ctx);
4965
5353
  if (got instanceof Response) return got;
4966
5354
  const db = got;
4967
5355
  const { meetings } = await db.query({ meetings: { $: { where: { id: m[1] }, limit: 1 } } });
@@ -4984,7 +5372,7 @@ var handleAdminMeetingCancel = async (req, url, env, ctx) => {
4984
5372
  var handleAdminApprove = async (req, url, env, ctx) => {
4985
5373
  const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)\/approve$/);
4986
5374
  if (req.method !== "POST" || !m) return null;
4987
- const got = await gate3(req, env, ctx);
5375
+ const got = await gate4(req, env, ctx);
4988
5376
  if (got instanceof Response) return got;
4989
5377
  const db = got;
4990
5378
  const id = m[1];
@@ -4997,7 +5385,7 @@ var handleAdminApprove = async (req, url, env, ctx) => {
4997
5385
  var handleAdminRefund = async (req, url, env, ctx) => {
4998
5386
  const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)\/refund$/);
4999
5387
  if (req.method !== "POST" || !m) return null;
5000
- const got = await gate3(req, env, ctx);
5388
+ const got = await gate4(req, env, ctx);
5001
5389
  if (got instanceof Response) return got;
5002
5390
  const db = got;
5003
5391
  const app = await loadApp(db, m[1]);
@@ -5029,7 +5417,7 @@ var handleAdminRefund = async (req, url, env, ctx) => {
5029
5417
  var handleAdminApplicationPatch = async (req, url, env, ctx) => {
5030
5418
  const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)$/);
5031
5419
  if (req.method !== "PATCH" || !m) return null;
5032
- const got = await gate3(req, env, ctx);
5420
+ const got = await gate4(req, env, ctx);
5033
5421
  if (got instanceof Response) return got;
5034
5422
  const db = got;
5035
5423
  const id = m[1];
@@ -5057,7 +5445,7 @@ var handleAdminApplicationPatch = async (req, url, env, ctx) => {
5057
5445
  };
5058
5446
 
5059
5447
  // src/worker-routes-admin-comms.ts
5060
- async function gate4(req, env, ctx) {
5448
+ async function gate5(req, env, ctx) {
5061
5449
  const rawDb = ctx.makeDb(env);
5062
5450
  const u = await ctx.verifyUser(req, env);
5063
5451
  if (!u) return json({ error: "unauthorized" }, 401);
@@ -5079,7 +5467,7 @@ var notifyDeps = (db, env) => ({
5079
5467
  });
5080
5468
  var handleAdminGroupEmail = async (req, url, env, ctx) => {
5081
5469
  if (url.pathname !== "/api/admin/group/email" || req.method !== "GET" && req.method !== "PUT") return null;
5082
- const got = await gate4(req, env, ctx);
5470
+ const got = await gate5(req, env, ctx);
5083
5471
  if (got instanceof Response) return got;
5084
5472
  const db = got;
5085
5473
  const group = await loadGroup2(db, ctx.chapter.id);
@@ -5141,7 +5529,7 @@ var handleAdminGroupEmail = async (req, url, env, ctx) => {
5141
5529
  };
5142
5530
  var handleAdminEmailLog = async (req, url, env, ctx) => {
5143
5531
  if (req.method !== "GET" || url.pathname !== "/api/admin/email/log") return null;
5144
- const got = await gate4(req, env, ctx);
5532
+ const got = await gate5(req, env, ctx);
5145
5533
  if (got instanceof Response) return got;
5146
5534
  const { emailLog } = await got.query({ emailLog: { $: { order: { sentAt: "desc" }, limit: 50 } } });
5147
5535
  const sends = (emailLog ?? []).map((r) => ({
@@ -5158,7 +5546,7 @@ var handleAdminEmailLog = async (req, url, env, ctx) => {
5158
5546
  };
5159
5547
  var handleAdminEmailTest = async (req, url, env, ctx) => {
5160
5548
  if (req.method !== "POST" || url.pathname !== "/api/admin/email/test") return null;
5161
- const got = await gate4(req, env, ctx);
5549
+ const got = await gate5(req, env, ctx);
5162
5550
  if (got instanceof Response) return got;
5163
5551
  const db = got;
5164
5552
  let body;
@@ -5186,7 +5574,7 @@ var handleAdminEmailTest = async (req, url, env, ctx) => {
5186
5574
  var handleAdminComms = async (req, url, env, ctx) => {
5187
5575
  const m = url.pathname.match(/^\/api\/admin\/people\/([0-9a-fA-F-]+)\/comms$/);
5188
5576
  if (req.method !== "GET" || !m) return null;
5189
- const got = await gate4(req, env, ctx);
5577
+ const got = await gate5(req, env, ctx);
5190
5578
  if (got instanceof Response) return got;
5191
5579
  const db = got;
5192
5580
  const appId = m[1];
@@ -5224,26 +5612,12 @@ var handleAdminComms = async (req, url, env, ctx) => {
5224
5612
  };
5225
5613
 
5226
5614
  // src/worker-routes-network.ts
5227
- import { getRecord, typeSummary } from "@odla-ai/crm";
5228
- import { signFederatedRequest as signFederatedRequest2, verifyFederatedRequest as verifyFederatedRequest5 } from "@odla-ai/db";
5229
-
5230
- // src/worker-network-fetch.ts
5231
- function targetFetcher(env, target) {
5232
- if (!target.binding) return null;
5233
- const candidate = env[target.binding];
5234
- if (typeof candidate !== "object" || candidate === null || !("fetch" in candidate) || typeof candidate.fetch !== "function") {
5235
- throw new Error(`service binding "${target.binding}" is unavailable`);
5236
- }
5237
- return candidate;
5238
- }
5239
- function fetchNetworkTarget(env, target, destination, init) {
5240
- const binding = targetFetcher(env, target);
5241
- return binding ? binding.fetch(new Request(destination, init)) : globalThis.fetch(destination, init);
5242
- }
5615
+ import { getRecord as getRecord3, typeSummary } from "@odla-ai/crm";
5616
+ import { signFederatedRequest as signFederatedRequest3, verifyFederatedRequest as verifyFederatedRequest5 } from "@odla-ai/db";
5243
5617
 
5244
5618
  // src/worker-network-push.ts
5245
5619
  import { recordDeliveryAttempt } from "@odla-ai/crm";
5246
- import { signFederatedRequest } from "@odla-ai/db";
5620
+ import { signFederatedRequest as signFederatedRequest2 } from "@odla-ai/db";
5247
5621
  async function pushNetworkRecord(db, env, ctx, target, record4) {
5248
5622
  const secret = await getVaultSecret(db, target.secretName);
5249
5623
  const crmDeps3 = {
@@ -5277,7 +5651,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
5277
5651
  const payloadBody = JSON.stringify(payload);
5278
5652
  const destination = new URL("/api/network/shared", target.url);
5279
5653
  try {
5280
- const signed = await signFederatedRequest({
5654
+ const signed = await signFederatedRequest2({
5281
5655
  secret,
5282
5656
  sender: ctx.chapter.id,
5283
5657
  method: "POST",
@@ -5336,7 +5710,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
5336
5710
  }
5337
5711
 
5338
5712
  // src/worker-routes-network.ts
5339
- async function gate5(req, env, ctx) {
5713
+ async function gate6(req, env, ctx) {
5340
5714
  const db = ctx.makeDb(env);
5341
5715
  const user = await ctx.verifyUser(req, env);
5342
5716
  if (!user) return { response: json({ error: "unauthorized" }, 401) };
@@ -5345,7 +5719,7 @@ async function gate5(req, env, ctx) {
5345
5719
  }
5346
5720
  var handleAdminNetworkTargets = async (req, url, env, ctx) => {
5347
5721
  if (req.method !== "GET" || url.pathname !== "/api/admin/network/targets") return null;
5348
- const got = await gate5(req, env, ctx);
5722
+ const got = await gate6(req, env, ctx);
5349
5723
  if ("response" in got) return got.response;
5350
5724
  return json({
5351
5725
  targets: ctx.chapter.network.targets.map(({
@@ -5377,26 +5751,50 @@ var handleNetworkSnapshot = async (req, url, env, ctx) => {
5377
5751
  const types = await Promise.all(
5378
5752
  Object.keys(ctx.chapter.crm.config.types).map((type) => typeSummary({ crm: ctx.chapter.crm, db }, type))
5379
5753
  );
5754
+ const activeConfiguration = ctx.chapter.signupControl ? await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV) : null;
5380
5755
  const snapshot = {
5381
5756
  version: 1,
5382
5757
  site: { id: ctx.chapter.id, name: ctx.chapter.name, mode: ctx.chapter.mode },
5383
5758
  generatedAt: Date.now(),
5759
+ configuration: {
5760
+ activeRevision: activeConfiguration?.revision ?? null,
5761
+ ...activeConfiguration ? { digest: activeConfiguration.digest } : {}
5762
+ },
5384
5763
  types
5385
5764
  };
5386
5765
  return json(snapshot);
5387
5766
  };
5388
5767
  async function snapshotOne(db, env, ctx, target) {
5768
+ const transportKind = target.binding ? "binding" : "url";
5769
+ const base = {
5770
+ id: target.id,
5771
+ name: target.name,
5772
+ url: target.url,
5773
+ configuration: { activeRevision: null },
5774
+ publicSite: { available: null }
5775
+ };
5389
5776
  const secret = await getVaultSecret(db, target.secretName);
5390
- if (!secret) return { id: target.id, name: target.name, url: target.url, available: false, error: "edge secret is missing" };
5777
+ if (!secret) return {
5778
+ ...base,
5779
+ available: false,
5780
+ transport: {
5781
+ kind: transportKind,
5782
+ ...target.binding ? { binding: target.binding } : {},
5783
+ reachable: null,
5784
+ error: { code: "edge_secret_missing", message: "edge secret is missing" }
5785
+ },
5786
+ error: "edge secret is missing"
5787
+ };
5391
5788
  const destination = new URL("/api/network/snapshot", target.url);
5392
5789
  try {
5393
- const headers = await signFederatedRequest2({
5790
+ const transport = resolveNetworkTargetTransport(env, target);
5791
+ const headers = await signFederatedRequest3({
5394
5792
  secret,
5395
5793
  sender: ctx.chapter.id,
5396
5794
  method: "GET",
5397
5795
  url: destination
5398
5796
  });
5399
- const response = await fetchNetworkTarget(env, target, destination, {
5797
+ const response = await transport.fetch(destination, {
5400
5798
  headers,
5401
5799
  signal: AbortSignal.timeout(1e4)
5402
5800
  });
@@ -5404,34 +5802,53 @@ async function snapshotOne(db, env, ctx, target) {
5404
5802
  if (!response.ok || !body || !("version" in body) || body.version !== 1 || !("site" in body) || body.site.id !== target.id || !Array.isArray(body.types)) {
5405
5803
  const upstream = body && "error" in body && typeof body.error === "string" ? body.error : void 0;
5406
5804
  return {
5407
- id: target.id,
5408
- name: target.name,
5409
- url: target.url,
5805
+ ...base,
5410
5806
  available: false,
5807
+ transport: {
5808
+ kind: transport.kind,
5809
+ ...transport.binding ? { binding: transport.binding } : {},
5810
+ reachable: true
5811
+ },
5812
+ publicSite: { available: transport.kind === "url" ? true : null },
5411
5813
  error: upstream ?? (response.ok ? "invalid follower snapshot" : `follower returned ${response.status}`)
5412
5814
  };
5413
5815
  }
5414
5816
  return {
5415
- id: target.id,
5416
- name: target.name,
5417
- url: target.url,
5817
+ ...base,
5418
5818
  available: true,
5819
+ transport: {
5820
+ kind: transport.kind,
5821
+ ...transport.binding ? { binding: transport.binding } : {},
5822
+ reachable: true
5823
+ },
5824
+ configuration: body.configuration ?? { activeRevision: null },
5825
+ publicSite: { available: transport.kind === "url" ? true : null },
5419
5826
  generatedAt: body.generatedAt,
5420
5827
  types: body.types
5421
5828
  };
5422
5829
  } catch (error) {
5830
+ const bindingError = error instanceof NetworkTargetTransportError;
5831
+ const message = error instanceof Error ? error.message : "snapshot failed";
5423
5832
  return {
5424
- id: target.id,
5425
- name: target.name,
5426
- url: target.url,
5833
+ ...base,
5427
5834
  available: false,
5428
- error: error instanceof Error ? error.message : "snapshot failed"
5835
+ transport: {
5836
+ kind: transportKind,
5837
+ ...target.binding ? { binding: target.binding } : {},
5838
+ reachable: false,
5839
+ error: {
5840
+ code: bindingError ? "binding_unavailable" : target.binding ? "binding_request_failed" : "url_request_failed",
5841
+ message
5842
+ }
5843
+ },
5844
+ publicSite: { available: target.binding ? null : false },
5845
+ error: message
5429
5846
  };
5430
5847
  }
5431
5848
  }
5432
5849
  var handleAdminNetworkRollup = async (req, url, env, ctx) => {
5433
5850
  if (req.method !== "GET" || url.pathname !== "/api/admin/network/rollup") return null;
5434
- const got = await gate5(req, env, ctx);
5851
+ const got = await gate6(req, env, ctx);
5435
5852
  if ("response" in got) return got.response;
5436
5853
  const targets = await Promise.all(
5437
5854
  ctx.chapter.network.targets.map((target) => snapshotOne(got.db, env, ctx, target))
@@ -5458,7 +5875,7 @@ var handleAdminNetworkRollup = async (req, url, env, ctx) => {
5458
5875
  };
5459
5876
  var handleAdminNetworkPush = async (req, url, env, ctx) => {
5460
5877
  if (req.method !== "POST" || url.pathname !== "/api/admin/network/push") return null;
5461
- const got = await gate5(req, env, ctx);
5878
+ const got = await gate6(req, env, ctx);
5462
5879
  if ("response" in got) return got.response;
5463
5880
  let body;
5464
5881
  try {
@@ -5473,7 +5890,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
5473
5890
  if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
5474
5891
  const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
5475
5892
  if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
5476
- const record4 = await getRecord({ crm: ctx.chapter.crm, db: got.db }, body.recordId);
5893
+ const record4 = await getRecord3({ crm: ctx.chapter.crm, db: got.db }, body.recordId);
5477
5894
  if (!record4) return json({ error: "record not found" }, 404);
5478
5895
  const results = await Promise.all(
5479
5896
  targets.map((target) => pushNetworkRecord(got.db, env, ctx, target, record4))
@@ -5482,7 +5899,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
5482
5899
  };
5483
5900
 
5484
5901
  // src/worker-routes-network-records-follower.ts
5485
- import { CRM_NS, CrmInputError, getRecord as getRecord2, listRecords, setStage as setStage2, updateRecord as updateRecord3 } from "@odla-ai/crm";
5902
+ import { CRM_NS, CrmInputError, getRecord as getRecord4, listRecords, setStage as setStage2, updateRecord as updateRecord3 } from "@odla-ai/crm";
5486
5903
  import { verifyFederatedRequest as verifyFederatedRequest6 } from "@odla-ai/db";
5487
5904
 
5488
5905
  // src/worker-routes-network-records-common.ts
@@ -5492,7 +5909,7 @@ var MAX_OFFSET = 1e4;
5492
5909
  var MAX_SEARCH = 200;
5493
5910
  var MAX_BODY = 32768;
5494
5911
  var RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9:_-]{0,191}$/;
5495
- var MUTATION_ID = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
5912
+ var MUTATION_ID2 = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
5496
5913
  function bodyTooLarge(body) {
5497
5914
  return new TextEncoder().encode(body).byteLength > MAX_BODY;
5498
5915
  }
@@ -5610,7 +6027,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5610
6027
  const hasFields = Boolean(fields && typeof fields === "object" && !Array.isArray(fields));
5611
6028
  const hasStage = typeof input?.stage === "string";
5612
6029
  const actor = input?.actor;
5613
- 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) {
6030
+ 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) {
5614
6031
  return json({ error: "invalid network mutation", code: "invalid_mutation" }, 422);
5615
6032
  }
5616
6033
  const editable = reader.editableFields[input.type];
@@ -5629,7 +6046,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5629
6046
  if (prior.fingerprint !== fingerprint) {
5630
6047
  return json({ error: "mutationId was already used for different input", code: "mutation_conflict" }, 409);
5631
6048
  }
5632
- const replayed = await getRecord2({ crm: ctx.chapter.crm, db }, input.recordId);
6049
+ const replayed = await getRecord4({ crm: ctx.chapter.crm, db }, input.recordId);
5633
6050
  if (!replayed || replayed.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
5634
6051
  const result = {
5635
6052
  version: 1,
@@ -5639,7 +6056,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5639
6056
  };
5640
6057
  return json(result);
5641
6058
  }
5642
- const record4 = await getRecord2({ crm: ctx.chapter.crm, db }, input.recordId);
6059
+ const record4 = await getRecord4({ crm: ctx.chapter.crm, db }, input.recordId);
5643
6060
  if (!record4 || record4.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
5644
6061
  if (record4.updatedAt !== input.expectedRevision) {
5645
6062
  return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
@@ -5724,15 +6141,15 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
5724
6141
  };
5725
6142
 
5726
6143
  // src/worker-routes-network-records-admin.ts
5727
- import { signFederatedRequest as signFederatedRequest3 } from "@odla-ai/db";
5728
- async function gate6(req, env, ctx) {
6144
+ import { signFederatedRequest as signFederatedRequest4 } from "@odla-ai/db";
6145
+ async function gate7(req, env, ctx) {
5729
6146
  const db = ctx.makeDb(env);
5730
6147
  const user = await ctx.verifyUser(req, env);
5731
6148
  if (!user) return { response: json({ error: "unauthorized" }, 401) };
5732
6149
  if (!await ctx.isAdmin(db, user)) return { response: json({ error: "forbidden" }, 403) };
5733
6150
  return { db, user };
5734
6151
  }
5735
- function targetFor(ctx, id) {
6152
+ function targetFor2(ctx, id) {
5736
6153
  return ctx.chapter.network.targets.find((target) => target.id === id);
5737
6154
  }
5738
6155
  async function readTargetRecords(db, env, ctx, target, query) {
@@ -5741,7 +6158,7 @@ async function readTargetRecords(db, env, ctx, target, query) {
5741
6158
  const destination = new URL("/api/network/records", target.url);
5742
6159
  destination.search = query.toString();
5743
6160
  try {
5744
- const headers = await signFederatedRequest3({ secret, sender: ctx.chapter.id, method: "GET", url: destination });
6161
+ const headers = await signFederatedRequest4({ secret, sender: ctx.chapter.id, method: "GET", url: destination });
5745
6162
  const response = await fetchNetworkTarget(env, target, destination, {
5746
6163
  headers,
5747
6164
  signal: AbortSignal.timeout(1e4)
@@ -5777,7 +6194,7 @@ async function mutateTargetRecord(db, env, ctx, target, input) {
5777
6194
  const destination = new URL("/api/network/records", target.url);
5778
6195
  const body = JSON.stringify(input);
5779
6196
  try {
5780
- const headers = await signFederatedRequest3({
6197
+ const headers = await signFederatedRequest4({
5781
6198
  secret,
5782
6199
  sender: ctx.chapter.id,
5783
6200
  method: "PATCH",
@@ -5809,7 +6226,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
5809
6226
  if (req.method === "PATCH" && Number(req.headers.get("content-length") ?? 0) > MAX_BODY) {
5810
6227
  return json({ error: "mutation body is too large", code: "body_too_large" }, 413);
5811
6228
  }
5812
- const got = await gate6(req, env, ctx);
6229
+ const got = await gate7(req, env, ctx);
5813
6230
  if ("response" in got) return got.response;
5814
6231
  if (req.method === "PATCH") {
5815
6232
  let input;
@@ -5822,10 +6239,10 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
5822
6239
  } catch {
5823
6240
  return json({ error: "invalid JSON body", code: "invalid_json" }, 422);
5824
6241
  }
5825
- const target2 = targetFor(ctx, input?.targetId);
6242
+ const target2 = targetFor2(ctx, input?.targetId);
5826
6243
  const hasFields = Boolean(input?.fields && typeof input.fields === "object" && !Array.isArray(input.fields));
5827
6244
  const hasStage = typeof input?.stage === "string";
5828
- 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) {
6245
+ 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) {
5829
6246
  return json({ error: "invalid network mutation", code: "invalid_mutation" }, 422);
5830
6247
  }
5831
6248
  return mutateTargetRecord(got.db, env, ctx, target2, {
@@ -5840,7 +6257,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
5840
6257
  }
5841
6258
  const targetId = url.searchParams.get("targetId") ?? "";
5842
6259
  const type = url.searchParams.get("type") ?? "";
5843
- const target = targetFor(ctx, targetId);
6260
+ const target = targetFor2(ctx, targetId);
5844
6261
  if (!target || !IDENTIFIER.test(type)) return json({ error: "configured targetId and record type are required" }, 400);
5845
6262
  const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
5846
6263
  const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
@@ -5857,8 +6274,8 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
5857
6274
  // src/worker-routes-network-notes.ts
5858
6275
  var IDENTIFIER2 = /^[a-z][a-zA-Z0-9_]*$/;
5859
6276
  var RECORD_ID2 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
5860
- var MAX_NOTE2 = 4e3;
5861
- async function gate7(req, env, ctx) {
6277
+ var MAX_NOTE3 = 4e3;
6278
+ async function gate8(req, env, ctx) {
5862
6279
  const db = ctx.makeDb(env);
5863
6280
  const user = await ctx.verifyUser(req, env);
5864
6281
  if (!user) return { response: json({ error: "unauthorized" }, 401) };
@@ -5892,7 +6309,7 @@ function asNote(row) {
5892
6309
  }
5893
6310
  var handleAdminNetworkNotes = async (req, url, env, ctx) => {
5894
6311
  if (url.pathname !== "/api/admin/network/notes" || req.method !== "GET" && req.method !== "POST") return null;
5895
- const got = await gate7(req, env, ctx);
6312
+ const got = await gate8(req, env, ctx);
5896
6313
  if ("response" in got) return got.response;
5897
6314
  const identity = noteIdentity(url, ctx);
5898
6315
  if (!identity) return json({ error: "valid targetId, type, and recordId are required" }, 400);
@@ -5909,7 +6326,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
5909
6326
  return json({ notes: (result.networkNotes ?? []).map((row) => asNote(row)) });
5910
6327
  }
5911
6328
  const contentLength = Number(req.headers.get("content-length") ?? 0);
5912
- if (contentLength > MAX_NOTE2 * 2) return json({ error: "note is too large" }, 413);
6329
+ if (contentLength > MAX_NOTE3 * 2) return json({ error: "note is too large" }, 413);
5913
6330
  let input;
5914
6331
  try {
5915
6332
  input = await req.json();
@@ -5917,7 +6334,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
5917
6334
  return json({ error: "invalid JSON body" }, 400);
5918
6335
  }
5919
6336
  const body = typeof input.body === "string" ? input.body.trim() : "";
5920
- if (!body || body.length > MAX_NOTE2) return json({ error: `note must be 1\u2013${MAX_NOTE2} characters` }, 400);
6337
+ if (!body || body.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
5921
6338
  const id = crypto.randomUUID();
5922
6339
  const note = {
5923
6340
  id,
@@ -5942,15 +6359,15 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
5942
6359
  import {
5943
6360
  addActivity,
5944
6361
  CRM_NS as CRM_NS2,
5945
- getRecord as getRecord3
6362
+ getRecord as getRecord5
5946
6363
  } from "@odla-ai/crm";
5947
- import { signFederatedRequest as signFederatedRequest4, verifyFederatedRequest as verifyFederatedRequest7 } from "@odla-ai/db";
6364
+ import { signFederatedRequest as signFederatedRequest5, verifyFederatedRequest as verifyFederatedRequest7 } from "@odla-ai/db";
5948
6365
  var IDENTIFIER3 = /^[a-z][a-zA-Z0-9_]*$/;
5949
6366
  var RECORD_ID3 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
5950
- var MUTATION_ID2 = /^[A-Za-z0-9_-]{16,128}$/;
5951
- var MAX_NOTE3 = 4e3;
6367
+ var MUTATION_ID3 = /^[A-Za-z0-9_-]{16,128}$/;
6368
+ var MAX_NOTE4 = 4e3;
5952
6369
  var NETWORK_META = "chapterNetwork";
5953
- async function gate8(req, env, ctx) {
6370
+ async function gate9(req, env, ctx) {
5954
6371
  const db = ctx.makeDb(env);
5955
6372
  const user = await ctx.verifyUser(req, env);
5956
6373
  if (!user) return { response: json({ error: "unauthorized" }, 401) };
@@ -5996,7 +6413,7 @@ function sourceLabel(sourceId) {
5996
6413
  }
5997
6414
  var handleNetworkSharedNotes = async (req, url, env, ctx) => {
5998
6415
  if (url.pathname !== "/api/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
5999
- if (req.method === "POST" && Number(req.headers.get("content-length") ?? 0) > MAX_NOTE3 * 2) {
6416
+ if (req.method === "POST" && Number(req.headers.get("content-length") ?? 0) > MAX_NOTE4 * 2) {
6000
6417
  return json({ error: "note is too large" }, 413);
6001
6418
  }
6002
6419
  const db = ctx.makeDb(env);
@@ -6014,7 +6431,7 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6014
6431
  if (!IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId) || !reader?.sharedNotes.includes(recordType)) {
6015
6432
  return json({ error: "shared notes are not enabled for this reader and record type" }, 403);
6016
6433
  }
6017
- const record4 = await getRecord3({ crm: ctx.chapter.crm, db }, recordId);
6434
+ const record4 = await getRecord5({ crm: ctx.chapter.crm, db }, recordId);
6018
6435
  if (!record4 || record4.type !== recordType) return json({ error: "record not found" }, 404);
6019
6436
  if (req.method === "GET") {
6020
6437
  return json({
@@ -6031,8 +6448,8 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6031
6448
  }
6032
6449
  const body = typeof input.body === "string" ? input.body.trim() : "";
6033
6450
  const mutationId = typeof input.mutationId === "string" ? input.mutationId : "";
6034
- if (!body || body.length > MAX_NOTE3 || !MUTATION_ID2.test(mutationId)) {
6035
- return json({ error: `body must be 1\u2013${MAX_NOTE3} characters and mutationId must be valid` }, 400);
6451
+ if (!body || body.length > MAX_NOTE4 || !MUTATION_ID3.test(mutationId)) {
6452
+ return json({ error: `body must be 1\u2013${MAX_NOTE4} characters and mutationId must be valid` }, 400);
6036
6453
  }
6037
6454
  const createdAt = Date.now();
6038
6455
  const id = `network:${verified.sender}:${mutationId}`;
@@ -6069,17 +6486,17 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6069
6486
  };
6070
6487
  return json({ version: 1, site: { id: ctx.chapter.id, name: ctx.chapter.name }, note, duplicate: result.duplicate }, 201);
6071
6488
  };
6072
- function targetFor2(ctx, id) {
6489
+ function targetFor3(ctx, id) {
6073
6490
  return ctx.chapter.network.targets.find((target) => target.id === id);
6074
6491
  }
6075
6492
  var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
6076
6493
  if (url.pathname !== "/api/admin/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
6077
- const got = await gate8(req, env, ctx);
6494
+ const got = await gate9(req, env, ctx);
6078
6495
  if ("response" in got) return got.response;
6079
6496
  const targetId = url.searchParams.get("targetId") ?? "";
6080
6497
  const recordType = url.searchParams.get("type") ?? "";
6081
6498
  const recordId = url.searchParams.get("recordId") ?? "";
6082
- const target = targetFor2(ctx, targetId);
6499
+ const target = targetFor3(ctx, targetId);
6083
6500
  if (!target || !target.sharedNotes.includes(recordType) || !IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId)) {
6084
6501
  return json({ error: "configured targetId, type, and recordId are required" }, 400);
6085
6502
  }
@@ -6092,7 +6509,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
6092
6509
  return json({ error: "invalid JSON body" }, 400);
6093
6510
  }
6094
6511
  const note = typeof input.body === "string" ? input.body.trim() : "";
6095
- if (!note || note.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
6512
+ if (!note || note.length > MAX_NOTE4) return json({ error: `note must be 1\u2013${MAX_NOTE4} characters` }, 400);
6096
6513
  body = JSON.stringify({ body: note, mutationId: crypto.randomUUID() });
6097
6514
  }
6098
6515
  const secret = await getVaultSecret(got.db, target.secretName);
@@ -6100,7 +6517,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
6100
6517
  const destination = new URL("/api/network/shared-notes", target.url);
6101
6518
  destination.search = new URLSearchParams({ type: recordType, recordId }).toString();
6102
6519
  try {
6103
- const headers = await signFederatedRequest4({
6520
+ const headers = await signFederatedRequest5({
6104
6521
  secret,
6105
6522
  sender: ctx.chapter.id,
6106
6523
  method: req.method,
@@ -6692,11 +7109,11 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
6692
7109
  };
6693
7110
 
6694
7111
  // src/signup-control-delivery.ts
6695
- import { signFederatedRequest as signFederatedRequest5 } from "@odla-ai/db";
7112
+ import { signFederatedRequest as signFederatedRequest6 } from "@odla-ai/db";
6696
7113
  async function deliverSignupControl(options) {
6697
7114
  const destination = new URL(SIGNUP_CONTROL_PATH, options.targetUrl);
6698
7115
  const body = JSON.stringify(options.envelope);
6699
- const signed = await signFederatedRequest5({
7116
+ const signed = await signFederatedRequest6({
6700
7117
  secret: options.secret,
6701
7118
  sender: options.sender,
6702
7119
  method: "POST",
@@ -6717,11 +7134,11 @@ async function deliverSignupControl(options) {
6717
7134
  }
6718
7135
 
6719
7136
  // src/member-content-delivery.ts
6720
- import { signFederatedRequest as signFederatedRequest6 } from "@odla-ai/db";
7137
+ import { signFederatedRequest as signFederatedRequest7 } from "@odla-ai/db";
6721
7138
  async function deliverMemberContent(options) {
6722
7139
  const destination = new URL(MEMBER_CONTENT_CONTROL_PATH, options.targetUrl);
6723
7140
  const body = JSON.stringify(options.envelope);
6724
- const signed = await signFederatedRequest6({
7141
+ const signed = await signFederatedRequest7({
6725
7142
  secret: options.secret,
6726
7143
  sender: options.sender,
6727
7144
  method: "POST",
@@ -6742,11 +7159,11 @@ async function deliverMemberContent(options) {
6742
7159
  }
6743
7160
 
6744
7161
  // src/builder-library-delivery.ts
6745
- import { signFederatedRequest as signFederatedRequest7 } from "@odla-ai/db";
7162
+ import { signFederatedRequest as signFederatedRequest8 } from "@odla-ai/db";
6746
7163
  async function deliverBuilderLibrary(options) {
6747
7164
  const destination = new URL(BUILDER_LIBRARY_CONTROL_PATH, options.targetUrl);
6748
7165
  const body = JSON.stringify(options.envelope);
6749
- const signed = await signFederatedRequest7({ secret: options.secret, sender: options.sender, method: "POST", url: destination, body });
7166
+ const signed = await signFederatedRequest8({ secret: options.secret, sender: options.sender, method: "POST", url: destination, body });
6750
7167
  const response = await (options.fetch ?? globalThis.fetch)(destination, { method: "POST", headers: { ...signed, "content-type": "application/json" }, body });
6751
7168
  const result = await response.json();
6752
7169
  if (!response.ok || !("ok" in result) || result.ok !== true) {
@@ -6756,31 +7173,6 @@ async function deliverBuilderLibrary(options) {
6756
7173
  return result;
6757
7174
  }
6758
7175
 
6759
- // src/admission-grants-delivery.ts
6760
- import { signFederatedRequest as signFederatedRequest8 } from "@odla-ai/db";
6761
- async function deliverAdmissionGrants(options) {
6762
- const destination = new URL(ADMISSION_GRANTS_PATH, options.targetUrl);
6763
- const body = JSON.stringify(options.envelope);
6764
- const signed = await signFederatedRequest8({
6765
- secret: options.secret,
6766
- sender: options.sender,
6767
- method: "POST",
6768
- url: destination,
6769
- body
6770
- });
6771
- const response = await (options.fetch ?? globalThis.fetch)(destination, {
6772
- method: "POST",
6773
- headers: { ...signed, "content-type": "application/json" },
6774
- body
6775
- });
6776
- const result = await response.json();
6777
- if (!response.ok || !("ok" in result) || result.ok !== true) {
6778
- const failure = result;
6779
- throw new Error(`admission grants rejected (${response.status}): ${failure.reason ?? failure.error ?? "unknown"}`);
6780
- }
6781
- return result;
6782
- }
6783
-
6784
7176
  // src/signup-control-theme.ts
6785
7177
  var SIGNUP_CONTROL_THEME_MARKER = "<!--odla:signup-control-theme-->";
6786
7178
  var SIGNUP_CONTROL_THEME_STYLE_ID = "odla-signup-control-theme";
@@ -6940,6 +7332,7 @@ var BUILTIN_ROUTES = [
6940
7332
  handleAdminPeopleRole,
6941
7333
  handleAdminCrmSync,
6942
7334
  handleAdminAdmissionGrants,
7335
+ handleAdminNetworkAdmissions,
6943
7336
  handleAdminClerkPrivateProfileSync,
6944
7337
  // Aggregation
6945
7338
  handleAdminDashboard,
@@ -6984,6 +7377,7 @@ function chapterWorker(options) {
6984
7377
  };
6985
7378
  }
6986
7379
  export {
7380
+ AdmissionGrantDeliveryError,
6987
7381
  chapterWorker,
6988
7382
  createWorkerContext,
6989
7383
  deliverAdmissionGrants,
@@ -6991,6 +7385,7 @@ export {
6991
7385
  deliverMemberContent,
6992
7386
  deliverSignupControl,
6993
7387
  renderActiveSignupControlHtml,
7388
+ retryLeaderAdmissionDeliveries,
6994
7389
  withActiveSignupControlTheme
6995
7390
  };
6996
7391
  //# sourceMappingURL=index.js.map