@odla-ai/chapter 0.42.1 → 0.42.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -282,6 +282,9 @@ interface ChapterNetworkTarget {
282
282
  /** Record types on which this leader permits remote lifecycle transitions.
283
283
  * The follower must independently opt in and remains pipeline authority. */
284
284
  stageTransitions?: readonly string[];
285
+ /** Allow package-owned desired admission decisions for existing leader CRM
286
+ * people to be delivered to this target. Omitted means default deny. */
287
+ admissionGrants?: boolean;
285
288
  }
286
289
  /** One signed peer allowed to browse a privacy-bounded projection of this
287
290
  * site's CRM. The follower owns this allowlist; a leader target declaration
@@ -291,7 +294,7 @@ interface ChapterNetworkReader {
291
294
  id: string;
292
295
  /** Per-record-type field allowlist. Omitted types and fields never leave
293
296
  * this site. Record id, name, stage, and timestamps are bounded metadata. */
294
- fields: Record<string, readonly string[]>;
297
+ fields?: Record<string, readonly string[]>;
295
298
  /** Record types on which this reader may append an explicitly shared note.
296
299
  * Omitted by default: browse permission never silently grants write access.
297
300
  * Shared notes land in the follower CRM activity feed. */
@@ -303,6 +306,9 @@ interface ChapterNetworkReader {
303
306
  /** Local CRM record types on which this reader may request a lifecycle
304
307
  * transition. Chapter still enforces the local pipeline and gates. */
305
308
  stageTransitions?: readonly string[];
309
+ /** Accept signed desired admission decisions from this reader. Independent
310
+ * of record browsing; an admission-only reader may omit `fields`. */
311
+ admissionGrants?: boolean;
306
312
  }
307
313
  /** Leader/follower network configuration. A follower needs no code config to
308
314
  * receive pushed records. It must explicitly declare `readers` before any peer
@@ -347,6 +353,7 @@ interface ResolvedNetworkTarget {
347
353
  sharedNotes: readonly string[];
348
354
  editableFields: Record<string, readonly string[]>;
349
355
  stageTransitions: readonly string[];
356
+ admissionGrants?: boolean;
350
357
  }
351
358
  /** A validated follower-owned record projection for one signed reader. */
352
359
  interface ResolvedNetworkReader {
@@ -355,6 +362,7 @@ interface ResolvedNetworkReader {
355
362
  sharedNotes: readonly string[];
356
363
  editableFields: Record<string, readonly string[]>;
357
364
  stageTransitions: readonly string[];
365
+ admissionGrants?: boolean;
358
366
  }
359
367
  /** Fully-resolved leader network configuration. */
360
368
  interface ResolvedNetwork {
@@ -956,7 +964,7 @@ declare function createChapterIntegration(chapter: Chapter, options?: ChapterInt
956
964
  * read-only super-admin tier (default on for the `"claim"` ladder). A `"claim"`
957
965
  * chapter therefore emits the reference namespace set — `applications`,
958
966
  * `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */
959
- declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean, application?: ResolvedApplication, includeSignupControl?: boolean, includeMemberContent?: boolean, includeBuilderLibrary?: boolean): {
967
+ declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean, application?: ResolvedApplication, includeSignupControl?: boolean, includeMemberContent?: boolean, includeBuilderLibrary?: boolean, includeLeaderAdmissions?: boolean): {
960
968
  schema: DbSchema;
961
969
  rules: DbRules;
962
970
  };
package/dist/index.d.ts CHANGED
@@ -282,6 +282,9 @@ interface ChapterNetworkTarget {
282
282
  /** Record types on which this leader permits remote lifecycle transitions.
283
283
  * The follower must independently opt in and remains pipeline authority. */
284
284
  stageTransitions?: readonly string[];
285
+ /** Allow package-owned desired admission decisions for existing leader CRM
286
+ * people to be delivered to this target. Omitted means default deny. */
287
+ admissionGrants?: boolean;
285
288
  }
286
289
  /** One signed peer allowed to browse a privacy-bounded projection of this
287
290
  * site's CRM. The follower owns this allowlist; a leader target declaration
@@ -291,7 +294,7 @@ interface ChapterNetworkReader {
291
294
  id: string;
292
295
  /** Per-record-type field allowlist. Omitted types and fields never leave
293
296
  * this site. Record id, name, stage, and timestamps are bounded metadata. */
294
- fields: Record<string, readonly string[]>;
297
+ fields?: Record<string, readonly string[]>;
295
298
  /** Record types on which this reader may append an explicitly shared note.
296
299
  * Omitted by default: browse permission never silently grants write access.
297
300
  * Shared notes land in the follower CRM activity feed. */
@@ -303,6 +306,9 @@ interface ChapterNetworkReader {
303
306
  /** Local CRM record types on which this reader may request a lifecycle
304
307
  * transition. Chapter still enforces the local pipeline and gates. */
305
308
  stageTransitions?: readonly string[];
309
+ /** Accept signed desired admission decisions from this reader. Independent
310
+ * of record browsing; an admission-only reader may omit `fields`. */
311
+ admissionGrants?: boolean;
306
312
  }
307
313
  /** Leader/follower network configuration. A follower needs no code config to
308
314
  * receive pushed records. It must explicitly declare `readers` before any peer
@@ -347,6 +353,7 @@ interface ResolvedNetworkTarget {
347
353
  sharedNotes: readonly string[];
348
354
  editableFields: Record<string, readonly string[]>;
349
355
  stageTransitions: readonly string[];
356
+ admissionGrants?: boolean;
350
357
  }
351
358
  /** A validated follower-owned record projection for one signed reader. */
352
359
  interface ResolvedNetworkReader {
@@ -355,6 +362,7 @@ interface ResolvedNetworkReader {
355
362
  sharedNotes: readonly string[];
356
363
  editableFields: Record<string, readonly string[]>;
357
364
  stageTransitions: readonly string[];
365
+ admissionGrants?: boolean;
358
366
  }
359
367
  /** Fully-resolved leader network configuration. */
360
368
  interface ResolvedNetwork {
@@ -956,7 +964,7 @@ declare function createChapterIntegration(chapter: Chapter, options?: ChapterInt
956
964
  * read-only super-admin tier (default on for the `"claim"` ladder). A `"claim"`
957
965
  * chapter therefore emits the reference namespace set — `applications`,
958
966
  * `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */
959
- declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean, application?: ResolvedApplication, includeSignupControl?: boolean, includeMemberContent?: boolean, includeBuilderLibrary?: boolean): {
967
+ declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean, application?: ResolvedApplication, includeSignupControl?: boolean, includeMemberContent?: boolean, includeBuilderLibrary?: boolean, includeLeaderAdmissions?: boolean): {
960
968
  schema: DbSchema;
961
969
  rules: DbRules;
962
970
  };
package/dist/index.js CHANGED
@@ -299,6 +299,50 @@ var admissionGrantRevisionsEntity = {
299
299
  receivedAt: attr2("number", { indexed: true })
300
300
  }
301
301
  };
302
+ var leaderAdmissionAssignmentsEntity = {
303
+ attrs: {
304
+ id: id(),
305
+ targetId: attr2("string", { indexed: true }),
306
+ personId: attr2("string", { indexed: true }),
307
+ grantId: attr2("string", { indexed: true }),
308
+ decision: attr2("string", { indexed: true }),
309
+ note: attr2("string", { optional: true }),
310
+ expiresAt: attr2("number", { optional: true, indexed: true }),
311
+ active: attr2("boolean", { indexed: true }),
312
+ lastMutationId: attr2("string", { indexed: true }),
313
+ lastRequestDigest: attr2("string"),
314
+ createdAt: attr2("number", { indexed: true }),
315
+ createdBy: attr2("string", { indexed: true }),
316
+ updatedAt: attr2("number", { indexed: true }),
317
+ updatedBy: attr2("string", { indexed: true })
318
+ }
319
+ };
320
+ var leaderAdmissionDeliveriesEntity = {
321
+ attrs: {
322
+ id: id(),
323
+ targetId: attr2("string", { indexed: true }),
324
+ environment: attr2("string", { indexed: true }),
325
+ revision: attr2("number", { indexed: true }),
326
+ digest: attr2("string", { indexed: true }),
327
+ envelope: attr2("json"),
328
+ status: attr2("string", { indexed: true }),
329
+ attempts: attr2("number", { indexed: true }),
330
+ lastAttemptAt: attr2("number", { optional: true, indexed: true }),
331
+ lastError: attr2("string", { optional: true }),
332
+ acknowledgedAt: attr2("number", { optional: true, indexed: true }),
333
+ updatedAt: attr2("number", { indexed: true })
334
+ }
335
+ };
336
+ var leaderAdmissionMutationsEntity = {
337
+ attrs: {
338
+ id: id(),
339
+ targetId: attr2("string", { indexed: true }),
340
+ personId: attr2("string", { indexed: true }),
341
+ requestDigest: attr2("string", { indexed: true }),
342
+ revision: attr2("number", { indexed: true }),
343
+ createdAt: attr2("number", { indexed: true })
344
+ }
345
+ };
302
346
 
303
347
  // src/schema-gifts.ts
304
348
  var attr3 = (type, flags = {}) => ({
@@ -570,7 +614,7 @@ var emailLog = {
570
614
  sentAt: attr4("number", { indexed: true })
571
615
  }
572
616
  };
573
- function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false, includeBuilderLibrary = false) {
617
+ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false, includeBuilderLibrary = false, includeLeaderAdmissions = false) {
574
618
  const entities = {};
575
619
  if (mode === "chapter") {
576
620
  entities.applications = applicationsFor(application);
@@ -598,6 +642,11 @@ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolv
598
642
  entities.builderLibraryControls = builderLibraryControls;
599
643
  entities.builderLibraryLocal = builderLibraryLocal;
600
644
  }
645
+ if (includeLeaderAdmissions) {
646
+ entities.leaderAdmissionAssignments = leaderAdmissionAssignmentsEntity;
647
+ entities.leaderAdmissionDeliveries = leaderAdmissionDeliveriesEntity;
648
+ entities.leaderAdmissionMutations = leaderAdmissionMutationsEntity;
649
+ }
601
650
  const schema = { entities, links: {} };
602
651
  const rules = {};
603
652
  for (const ns of Object.keys(entities)) {
@@ -1404,6 +1453,13 @@ function validateStageTransitions(opts) {
1404
1453
  throw new Error(`${opts.path}: must contain unique, readable CRM types with pipelines`);
1405
1454
  }
1406
1455
  }
1456
+ function validateAdmissionCapability(path, enabled, crm) {
1457
+ if (!enabled) return;
1458
+ const person = crm.config.types.person;
1459
+ if (!person?.emailField || person.fields[person.emailField]?.type !== "email") {
1460
+ throw new Error(`${path}: requires a CRM person type with an emailField`);
1461
+ }
1462
+ }
1407
1463
  function resolveNetwork(config, crm) {
1408
1464
  const seen = /* @__PURE__ */ new Set();
1409
1465
  const targets = [];
@@ -1477,6 +1533,11 @@ function resolveNetwork(config, crm) {
1477
1533
  crm,
1478
1534
  readable: readableFields
1479
1535
  });
1536
+ validateAdmissionCapability(
1537
+ `defineChapter.network.targets.${target.id}.admissionGrants`,
1538
+ target.admissionGrants,
1539
+ crm
1540
+ );
1480
1541
  targets.push({
1481
1542
  id: target.id,
1482
1543
  name: target.name?.trim() || target.id,
@@ -1486,7 +1547,8 @@ function resolveNetwork(config, crm) {
1486
1547
  ...fields ? { fields } : {},
1487
1548
  sharedNotes,
1488
1549
  editableFields,
1489
- stageTransitions
1550
+ stageTransitions,
1551
+ ...target.admissionGrants === true ? { admissionGrants: true } : {}
1490
1552
  });
1491
1553
  }
1492
1554
  const readerIds = /* @__PURE__ */ new Set();
@@ -1499,9 +1561,12 @@ function resolveNetwork(config, crm) {
1499
1561
  throw new Error(`defineChapter.network.readers: duplicate reader "${reader.id}"`);
1500
1562
  }
1501
1563
  readerIds.add(reader.id);
1502
- const entries = Object.entries(reader.fields ?? {});
1503
- if (entries.length === 0) {
1504
- throw new Error(`defineChapter.network.readers.${reader.id}.fields: at least one CRM type is required`);
1564
+ const readerFields = reader.fields ?? {};
1565
+ const entries = Object.entries(readerFields);
1566
+ if (entries.length === 0 && !reader.admissionGrants) {
1567
+ throw new Error(
1568
+ `defineChapter.network.readers.${reader.id}.fields: at least one CRM type is required unless this is an admission-only reader`
1569
+ );
1505
1570
  }
1506
1571
  for (const [type, names] of entries) {
1507
1572
  if (!FIELD.test(type) || names.length === 0 || names.some((name) => !FIELD.test(name))) {
@@ -1531,23 +1596,35 @@ function resolveNetwork(config, crm) {
1531
1596
  path: `defineChapter.network.readers.${reader.id}.sharedNotes`,
1532
1597
  values: sharedNotes,
1533
1598
  crm,
1534
- fields: reader.fields
1599
+ fields: readerFields
1535
1600
  });
1536
1601
  const editableFields = reader.editableFields ?? {};
1537
1602
  validateEditableFields({
1538
1603
  path: `defineChapter.network.readers.${reader.id}.editableFields`,
1539
1604
  values: editableFields,
1540
1605
  crm,
1541
- readable: reader.fields
1606
+ readable: readerFields
1542
1607
  });
1543
1608
  const stageTransitions = reader.stageTransitions ?? [];
1544
1609
  validateStageTransitions({
1545
1610
  path: `defineChapter.network.readers.${reader.id}.stageTransitions`,
1546
1611
  values: stageTransitions,
1547
1612
  crm,
1548
- readable: reader.fields
1613
+ readable: readerFields
1614
+ });
1615
+ validateAdmissionCapability(
1616
+ `defineChapter.network.readers.${reader.id}.admissionGrants`,
1617
+ reader.admissionGrants,
1618
+ crm
1619
+ );
1620
+ readers.push({
1621
+ id: reader.id,
1622
+ fields: readerFields,
1623
+ sharedNotes,
1624
+ editableFields,
1625
+ stageTransitions,
1626
+ ...reader.admissionGrants === true ? { admissionGrants: true } : {}
1549
1627
  });
1550
- readers.push({ id: reader.id, fields: reader.fields, sharedNotes, editableFields, stageTransitions });
1551
1628
  }
1552
1629
  return { targets, readers };
1553
1630
  }
@@ -1678,7 +1755,8 @@ function defineChapter(config) {
1678
1755
  application,
1679
1756
  Boolean(signupControl),
1680
1757
  Boolean(memberContent),
1681
- Boolean(builderLibrary)
1758
+ Boolean(builderLibrary),
1759
+ network.targets.some((target) => target.admissionGrants)
1682
1760
  );
1683
1761
  const services = config.services ?? ["db", "calendar", "o11y"];
1684
1762
  const account = config.account ?? "none";