@odla-ai/chapter 0.26.1 → 0.27.1

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
@@ -42,7 +42,7 @@ interface ChapterCopy {
42
42
  admin: {
43
43
  auth: TextFields<"checking" | "notAuthorized" | "accountNotAuthorized" | "thisAccount" | "signOut" | "loading" | "signInNotConfigured" | "missingPublishableKey" | "noWorkspaces" | "signInTagline">;
44
44
  shell: TextFields<"adminRole" | "adminConsole" | "adminName" | "navigationLabel">;
45
- workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "portfolio" | "settings" | "calendar" | "email" | "chapters" | "formations" | "dealFlow" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel" | "portfolioViewsLabel">;
45
+ workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "network" | "portfolio" | "settings" | "calendar" | "email" | "chapters" | "formations" | "dealFlow" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel" | "portfolioViewsLabel">;
46
46
  dashboard: TextFields<"loading" | "loadFailed" | "upcomingCalls" | "callsNeedAttention" | "noUpcomingCalls" | "drift" | "open" | "meet" | "applications" | "newMembers" | "newMembersUnavailable" | "revenueAdded" | "revenue" | "revenueUnavailable" | "pipeline" | "pipelineLabel" | "thisWeek" | "noChange" | "activeMemberships" | "annualRunRate" | "testMode">;
47
47
  billing: TextFields<"loading" | "loadFailed" | "notConfigured" | "active" | "annualized" | "renewingSoon" | "pastDue" | "subscriptions" | "testMode" | "truncated" | "name" | "email" | "application" | "subscription" | "cancelling" | "amount" | "renews">;
48
48
  availability: TextFields<"loading" | "loadFailed" | "saved" | "title" | "days" | "startHour" | "endHour" | "slotMinutes" | "minNoticeHours" | "windowDays" | "timezone" | "summaryTemplate" | "save"> & {
@@ -80,11 +80,31 @@ interface ChapterNetworkTarget {
80
80
  /** Per-record-type field allowlist. If present, omitted types cannot be sent
81
81
  * to this target. Without it, package-safe person/company defaults apply. */
82
82
  fields?: Record<string, readonly string[]>;
83
- }
84
- /** Leader/follower network configuration. Followers need no code config to
85
- * receive records; they only vault `network_share_secret`. */
83
+ /** Record types on which leader admins may deliberately share notes with
84
+ * this follower. Omitted by default: a target never gains write permission
85
+ * merely because its records are browsable. */
86
+ sharedNotes?: readonly string[];
87
+ }
88
+ /** One signed peer allowed to browse a privacy-bounded projection of this
89
+ * site's CRM. The follower owns this allowlist; a leader target declaration
90
+ * cannot widen it. */
91
+ interface ChapterNetworkReader {
92
+ /** Stable app/site slug expected in the signed federation envelope. */
93
+ id: string;
94
+ /** Per-record-type field allowlist. Omitted types and fields never leave
95
+ * this site. Record id, name, stage, and timestamps are bounded metadata. */
96
+ fields: Record<string, readonly string[]>;
97
+ /** Record types on which this reader may append an explicitly shared note.
98
+ * Omitted by default: browse permission never silently grants write access.
99
+ * Shared notes land in the follower CRM activity feed. */
100
+ sharedNotes?: readonly string[];
101
+ }
102
+ /** Leader/follower network configuration. A follower needs no code config to
103
+ * receive pushed records. It must explicitly declare `readers` before any peer
104
+ * can browse its CRM. */
86
105
  interface ChapterNetwork {
87
106
  targets?: readonly ChapterNetworkTarget[];
107
+ readers?: readonly ChapterNetworkReader[];
88
108
  }
89
109
  /** Public chapter-formation intake backed by one CRM record type. The host owns
90
110
  * the branded form; Chapter owns the bounded, idempotent write contract. */
@@ -119,10 +139,18 @@ interface ResolvedNetworkTarget {
119
139
  binding?: string;
120
140
  secretName: string;
121
141
  fields?: Record<string, readonly string[]>;
142
+ sharedNotes: readonly string[];
143
+ }
144
+ /** A validated follower-owned record projection for one signed reader. */
145
+ interface ResolvedNetworkReader {
146
+ id: string;
147
+ fields: Record<string, readonly string[]>;
148
+ sharedNotes: readonly string[];
122
149
  }
123
150
  /** Fully-resolved leader network configuration. */
124
151
  interface ResolvedNetwork {
125
152
  targets: readonly ResolvedNetworkTarget[];
153
+ readers: readonly ResolvedNetworkReader[];
126
154
  }
127
155
 
128
156
  /** Which feature profile a site runs. `chapter` is the full public member site
@@ -405,8 +433,8 @@ interface ChapterConfig {
405
433
  /** User-visible language for packaged Chapter surfaces. Recursively partial;
406
434
  * `defineChapter()` resolves every omitted leaf from the package defaults. */
407
435
  copy?: ChapterCopyInput;
408
- /** Leader follower delivery targets. Secret values stay in the leader
409
- * tenant vault; see {@link ChapterNetworkTarget}. */
436
+ /** Signed network edges: leader delivery targets and follower-owned,
437
+ * privacy-bounded record readers. Secret values stay in tenant vaults. */
410
438
  network?: ChapterNetwork;
411
439
  /** Enable a public, host-rendered formation application workflow. */
412
440
  formation?: LeaderFormationConfig;
@@ -589,9 +617,8 @@ interface ChapterIntegrationDescriptor {
589
617
  }>;
590
618
  /** Runbooks that bear on THIS chapter's role, so tooling can point at the
591
619
  * right procedure without the operator knowing it exists. A chapter declaring
592
- * `network.targets` is a leader hub; one declaring none is a follower site,
593
- * and the two follow different procedures for the same words ("connect",
594
- * "share records"). */
620
+ * `network.targets` is a leader hub; one with no targets is a follower site
621
+ * (and may declare `network.readers` for bounded CRM browsing). */
595
622
  runbooks: ChapterRunbookHints;
596
623
  }
597
624
  /** Which runbooks apply, and why. */
@@ -618,7 +645,7 @@ declare function createChapterIntegration(chapter: Chapter, options?: ChapterInt
618
645
  * read-only super-admin tier (default on for the `"claim"` ladder). A `"claim"`
619
646
  * chapter therefore emits the reference namespace set — `applications`,
620
647
  * `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */
621
- declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth): {
648
+ declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean): {
622
649
  schema: DbSchema;
623
650
  rules: DbRules;
624
651
  };
@@ -1070,6 +1097,52 @@ interface NetworkRollup {
1070
1097
  }>;
1071
1098
  targets: NetworkRollupTarget[];
1072
1099
  }
1100
+ /** Privacy-bounded CRM record returned by a follower to one configured,
1101
+ * authenticated reader. */
1102
+ interface NetworkRecord {
1103
+ id: string;
1104
+ type: string;
1105
+ name: string;
1106
+ stage?: string;
1107
+ fields: Record<string, unknown>;
1108
+ createdAt: number;
1109
+ updatedAt: number;
1110
+ }
1111
+ /** One bounded, offset-paged follower record window. */
1112
+ interface NetworkRecordPage {
1113
+ version: 1;
1114
+ site: {
1115
+ id: string;
1116
+ name: string;
1117
+ };
1118
+ type: string;
1119
+ records: NetworkRecord[];
1120
+ total: number;
1121
+ limit: number;
1122
+ offset: number;
1123
+ }
1124
+ /** Leader-private note attached to a stable remote record identity. */
1125
+ interface NetworkRecordNote {
1126
+ id: string;
1127
+ targetId: string;
1128
+ recordType: string;
1129
+ recordId: string;
1130
+ body: string;
1131
+ authorId: string;
1132
+ authorEmail?: string;
1133
+ createdAt: number;
1134
+ }
1135
+ /** A note deliberately shared across the federation edge and stored in the
1136
+ * follower CRM activity feed. Unlike NetworkRecordNote, this is visible to
1137
+ * the follower's own CRM admins. */
1138
+ interface NetworkSharedNote {
1139
+ id: string;
1140
+ sourceId: string;
1141
+ recordType: string;
1142
+ recordId: string;
1143
+ body: string;
1144
+ createdAt: number;
1145
+ }
1073
1146
  interface NormalizedSharedRecord {
1074
1147
  version: 1 | 2;
1075
1148
  sourceId: string;
@@ -1636,4 +1709,4 @@ type ApplicationBookingPatch = {
1636
1709
  * already there (never backward). */
1637
1710
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1638
1711
 
1639
- export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NetworkRollup, type NetworkRollupTarget, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, leaderCrmConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, verifyStripeSignature, webhookMutationId };
1712
+ export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkReader, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NetworkRecord, type NetworkRecordNote, type NetworkRecordPage, type NetworkRollup, type NetworkRollupTarget, type NetworkSharedNote, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkReader, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, leaderCrmConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, verifyStripeSignature, webhookMutationId };
package/dist/index.d.ts CHANGED
@@ -42,7 +42,7 @@ interface ChapterCopy {
42
42
  admin: {
43
43
  auth: TextFields<"checking" | "notAuthorized" | "accountNotAuthorized" | "thisAccount" | "signOut" | "loading" | "signInNotConfigured" | "missingPublishableKey" | "noWorkspaces" | "signInTagline">;
44
44
  shell: TextFields<"adminRole" | "adminConsole" | "adminName" | "navigationLabel">;
45
- workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "portfolio" | "settings" | "calendar" | "email" | "chapters" | "formations" | "dealFlow" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel" | "portfolioViewsLabel">;
45
+ workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "network" | "portfolio" | "settings" | "calendar" | "email" | "chapters" | "formations" | "dealFlow" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel" | "portfolioViewsLabel">;
46
46
  dashboard: TextFields<"loading" | "loadFailed" | "upcomingCalls" | "callsNeedAttention" | "noUpcomingCalls" | "drift" | "open" | "meet" | "applications" | "newMembers" | "newMembersUnavailable" | "revenueAdded" | "revenue" | "revenueUnavailable" | "pipeline" | "pipelineLabel" | "thisWeek" | "noChange" | "activeMemberships" | "annualRunRate" | "testMode">;
47
47
  billing: TextFields<"loading" | "loadFailed" | "notConfigured" | "active" | "annualized" | "renewingSoon" | "pastDue" | "subscriptions" | "testMode" | "truncated" | "name" | "email" | "application" | "subscription" | "cancelling" | "amount" | "renews">;
48
48
  availability: TextFields<"loading" | "loadFailed" | "saved" | "title" | "days" | "startHour" | "endHour" | "slotMinutes" | "minNoticeHours" | "windowDays" | "timezone" | "summaryTemplate" | "save"> & {
@@ -80,11 +80,31 @@ interface ChapterNetworkTarget {
80
80
  /** Per-record-type field allowlist. If present, omitted types cannot be sent
81
81
  * to this target. Without it, package-safe person/company defaults apply. */
82
82
  fields?: Record<string, readonly string[]>;
83
- }
84
- /** Leader/follower network configuration. Followers need no code config to
85
- * receive records; they only vault `network_share_secret`. */
83
+ /** Record types on which leader admins may deliberately share notes with
84
+ * this follower. Omitted by default: a target never gains write permission
85
+ * merely because its records are browsable. */
86
+ sharedNotes?: readonly string[];
87
+ }
88
+ /** One signed peer allowed to browse a privacy-bounded projection of this
89
+ * site's CRM. The follower owns this allowlist; a leader target declaration
90
+ * cannot widen it. */
91
+ interface ChapterNetworkReader {
92
+ /** Stable app/site slug expected in the signed federation envelope. */
93
+ id: string;
94
+ /** Per-record-type field allowlist. Omitted types and fields never leave
95
+ * this site. Record id, name, stage, and timestamps are bounded metadata. */
96
+ fields: Record<string, readonly string[]>;
97
+ /** Record types on which this reader may append an explicitly shared note.
98
+ * Omitted by default: browse permission never silently grants write access.
99
+ * Shared notes land in the follower CRM activity feed. */
100
+ sharedNotes?: readonly string[];
101
+ }
102
+ /** Leader/follower network configuration. A follower needs no code config to
103
+ * receive pushed records. It must explicitly declare `readers` before any peer
104
+ * can browse its CRM. */
86
105
  interface ChapterNetwork {
87
106
  targets?: readonly ChapterNetworkTarget[];
107
+ readers?: readonly ChapterNetworkReader[];
88
108
  }
89
109
  /** Public chapter-formation intake backed by one CRM record type. The host owns
90
110
  * the branded form; Chapter owns the bounded, idempotent write contract. */
@@ -119,10 +139,18 @@ interface ResolvedNetworkTarget {
119
139
  binding?: string;
120
140
  secretName: string;
121
141
  fields?: Record<string, readonly string[]>;
142
+ sharedNotes: readonly string[];
143
+ }
144
+ /** A validated follower-owned record projection for one signed reader. */
145
+ interface ResolvedNetworkReader {
146
+ id: string;
147
+ fields: Record<string, readonly string[]>;
148
+ sharedNotes: readonly string[];
122
149
  }
123
150
  /** Fully-resolved leader network configuration. */
124
151
  interface ResolvedNetwork {
125
152
  targets: readonly ResolvedNetworkTarget[];
153
+ readers: readonly ResolvedNetworkReader[];
126
154
  }
127
155
 
128
156
  /** Which feature profile a site runs. `chapter` is the full public member site
@@ -405,8 +433,8 @@ interface ChapterConfig {
405
433
  /** User-visible language for packaged Chapter surfaces. Recursively partial;
406
434
  * `defineChapter()` resolves every omitted leaf from the package defaults. */
407
435
  copy?: ChapterCopyInput;
408
- /** Leader follower delivery targets. Secret values stay in the leader
409
- * tenant vault; see {@link ChapterNetworkTarget}. */
436
+ /** Signed network edges: leader delivery targets and follower-owned,
437
+ * privacy-bounded record readers. Secret values stay in tenant vaults. */
410
438
  network?: ChapterNetwork;
411
439
  /** Enable a public, host-rendered formation application workflow. */
412
440
  formation?: LeaderFormationConfig;
@@ -589,9 +617,8 @@ interface ChapterIntegrationDescriptor {
589
617
  }>;
590
618
  /** Runbooks that bear on THIS chapter's role, so tooling can point at the
591
619
  * right procedure without the operator knowing it exists. A chapter declaring
592
- * `network.targets` is a leader hub; one declaring none is a follower site,
593
- * and the two follow different procedures for the same words ("connect",
594
- * "share records"). */
620
+ * `network.targets` is a leader hub; one with no targets is a follower site
621
+ * (and may declare `network.readers` for bounded CRM browsing). */
595
622
  runbooks: ChapterRunbookHints;
596
623
  }
597
624
  /** Which runbooks apply, and why. */
@@ -618,7 +645,7 @@ declare function createChapterIntegration(chapter: Chapter, options?: ChapterInt
618
645
  * read-only super-admin tier (default on for the `"claim"` ladder). A `"claim"`
619
646
  * chapter therefore emits the reference namespace set — `applications`,
620
647
  * `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */
621
- declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth): {
648
+ declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean): {
622
649
  schema: DbSchema;
623
650
  rules: DbRules;
624
651
  };
@@ -1070,6 +1097,52 @@ interface NetworkRollup {
1070
1097
  }>;
1071
1098
  targets: NetworkRollupTarget[];
1072
1099
  }
1100
+ /** Privacy-bounded CRM record returned by a follower to one configured,
1101
+ * authenticated reader. */
1102
+ interface NetworkRecord {
1103
+ id: string;
1104
+ type: string;
1105
+ name: string;
1106
+ stage?: string;
1107
+ fields: Record<string, unknown>;
1108
+ createdAt: number;
1109
+ updatedAt: number;
1110
+ }
1111
+ /** One bounded, offset-paged follower record window. */
1112
+ interface NetworkRecordPage {
1113
+ version: 1;
1114
+ site: {
1115
+ id: string;
1116
+ name: string;
1117
+ };
1118
+ type: string;
1119
+ records: NetworkRecord[];
1120
+ total: number;
1121
+ limit: number;
1122
+ offset: number;
1123
+ }
1124
+ /** Leader-private note attached to a stable remote record identity. */
1125
+ interface NetworkRecordNote {
1126
+ id: string;
1127
+ targetId: string;
1128
+ recordType: string;
1129
+ recordId: string;
1130
+ body: string;
1131
+ authorId: string;
1132
+ authorEmail?: string;
1133
+ createdAt: number;
1134
+ }
1135
+ /** A note deliberately shared across the federation edge and stored in the
1136
+ * follower CRM activity feed. Unlike NetworkRecordNote, this is visible to
1137
+ * the follower's own CRM admins. */
1138
+ interface NetworkSharedNote {
1139
+ id: string;
1140
+ sourceId: string;
1141
+ recordType: string;
1142
+ recordId: string;
1143
+ body: string;
1144
+ createdAt: number;
1145
+ }
1073
1146
  interface NormalizedSharedRecord {
1074
1147
  version: 1 | 2;
1075
1148
  sourceId: string;
@@ -1636,4 +1709,4 @@ type ApplicationBookingPatch = {
1636
1709
  * already there (never backward). */
1637
1710
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1638
1711
 
1639
- export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NetworkRollup, type NetworkRollupTarget, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, leaderCrmConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, verifyStripeSignature, webhookMutationId };
1712
+ export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkReader, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NetworkRecord, type NetworkRecordNote, type NetworkRecordPage, type NetworkRollup, type NetworkRollupTarget, type NetworkSharedNote, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkReader, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, leaderCrmConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, verifyStripeSignature, webhookMutationId };
package/dist/index.js CHANGED
@@ -27,6 +27,19 @@ var superAdmins = {
27
27
  createdAt: attr("number", { indexed: true })
28
28
  }
29
29
  };
30
+ var networkNotes = {
31
+ attrs: {
32
+ id: id(),
33
+ recordKey: attr("string", { indexed: true }),
34
+ targetId: attr("string", { indexed: true }),
35
+ recordType: attr("string", { indexed: true }),
36
+ recordId: attr("string", { indexed: true }),
37
+ body: attr("string"),
38
+ authorId: attr("string", { indexed: true }),
39
+ authorEmail: attr("string", { optional: true }),
40
+ createdAt: attr("number", { indexed: true })
41
+ }
42
+ };
30
43
  var applications = {
31
44
  attrs: {
32
45
  id: id(),
@@ -118,7 +131,7 @@ var emailLog = {
118
131
  sentAt: attr("number", { indexed: true })
119
132
  }
120
133
  };
121
- function chapterDb(mode, auth) {
134
+ function chapterDb(mode, auth, includeNetworkNotes = false) {
122
135
  const entities = {};
123
136
  if (mode === "chapter") {
124
137
  entities.applications = applications;
@@ -128,6 +141,7 @@ function chapterDb(mode, auth) {
128
141
  }
129
142
  if (auth.source === "table") entities.admins = admins;
130
143
  if (auth.superAdmins) entities.superAdmins = superAdmins;
144
+ if (includeNetworkNotes) entities.networkNotes = networkNotes;
131
145
  const schema = { entities, links: {} };
132
146
  const rules = {};
133
147
  for (const ns of Object.keys(entities)) {
@@ -519,6 +533,7 @@ var DEFAULT_ADMIN_COPY = {
519
533
  overview: "Overview",
520
534
  billing: "Billing",
521
535
  people: "People",
536
+ network: "Network",
522
537
  portfolio: "Portfolio",
523
538
  settings: "Settings",
524
539
  calendar: "Calendar",
@@ -880,10 +895,17 @@ function formationFields(crm, formation) {
880
895
  });
881
896
  }
882
897
 
883
- // src/config.ts
898
+ // src/config-network.ts
884
899
  var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
885
900
  var FIELD = /^[a-z][a-zA-Z0-9_]*$/;
886
901
  var BINDING = /^[A-Z][A-Z0-9_]{0,127}$/;
902
+ function validateSharedNotes(opts) {
903
+ const duplicate = opts.values.some((type, index) => opts.values.indexOf(type) !== index);
904
+ const invalid = opts.values.some((type) => !FIELD.test(type) || !opts.crm.config.types[type] || (opts.fields ? !opts.fields[type] : false));
905
+ if (duplicate || invalid) {
906
+ throw new Error(`${opts.path}: must contain unique CRM types allowed by fields`);
907
+ }
908
+ }
887
909
  function resolveNetwork(config, crm) {
888
910
  const seen = /* @__PURE__ */ new Set();
889
911
  const targets = [];
@@ -935,24 +957,81 @@ function resolveNetwork(config, crm) {
935
957
  `defineChapter.network.targets.${target.id}.binding: must be an uppercase Worker binding identifier`
936
958
  );
937
959
  }
960
+ const sharedNotes = target.sharedNotes ?? [];
961
+ validateSharedNotes({
962
+ path: `defineChapter.network.targets.${target.id}.sharedNotes`,
963
+ values: sharedNotes,
964
+ crm,
965
+ fields
966
+ });
938
967
  targets.push({
939
968
  id: target.id,
940
969
  name: target.name?.trim() || target.id,
941
970
  url: url.origin,
942
971
  ...binding ? { binding } : {},
943
972
  secretName,
944
- ...fields ? { fields } : {}
973
+ ...fields ? { fields } : {},
974
+ sharedNotes
945
975
  });
946
976
  }
947
- return { targets };
977
+ const readerIds = /* @__PURE__ */ new Set();
978
+ const readers = [];
979
+ for (const reader of config.network?.readers ?? []) {
980
+ if (!SLUG.test(reader.id)) {
981
+ throw new Error(`defineChapter.network.readers.id: must be a lowercase slug \u2014 got ${JSON.stringify(reader.id)}`);
982
+ }
983
+ if (readerIds.has(reader.id)) {
984
+ throw new Error(`defineChapter.network.readers: duplicate reader "${reader.id}"`);
985
+ }
986
+ readerIds.add(reader.id);
987
+ const entries = Object.entries(reader.fields ?? {});
988
+ if (entries.length === 0) {
989
+ throw new Error(`defineChapter.network.readers.${reader.id}.fields: at least one CRM type is required`);
990
+ }
991
+ for (const [type, names] of entries) {
992
+ if (!FIELD.test(type) || names.length === 0 || names.some((name) => !FIELD.test(name))) {
993
+ throw new Error(
994
+ `defineChapter.network.readers.${reader.id}.fields.${type}: type and field names must be non-empty CRM identifiers`
995
+ );
996
+ }
997
+ const def = crm.config.types[type];
998
+ if (!def) {
999
+ throw new Error(`defineChapter.network.readers.${reader.id}.fields: unknown local CRM type "${type}"`);
1000
+ }
1001
+ const unknown = names.filter((field) => !def.fields[field]);
1002
+ if (unknown.length > 0) {
1003
+ throw new Error(
1004
+ `defineChapter.network.readers.${reader.id}.fields.${type}: ${unknown.map((field) => `"${field}"`).join(", ")} not declared on the local CRM type`
1005
+ );
1006
+ }
1007
+ const nameField = def.nameField ?? "name";
1008
+ if (!names.includes(nameField)) {
1009
+ throw new Error(
1010
+ `defineChapter.network.readers.${reader.id}.fields.${type}: must include required name field "${nameField}"`
1011
+ );
1012
+ }
1013
+ }
1014
+ const sharedNotes = reader.sharedNotes ?? [];
1015
+ validateSharedNotes({
1016
+ path: `defineChapter.network.readers.${reader.id}.sharedNotes`,
1017
+ values: sharedNotes,
1018
+ crm,
1019
+ fields: reader.fields
1020
+ });
1021
+ readers.push({ id: reader.id, fields: reader.fields, sharedNotes });
1022
+ }
1023
+ return { targets, readers };
948
1024
  }
1025
+
1026
+ // src/config.ts
1027
+ var SLUG2 = /^[a-z0-9][a-z0-9-]{1,62}$/;
949
1028
  function isResolvedCrm(x) {
950
1029
  return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
951
1030
  }
952
1031
  function defineChapter(config) {
953
1032
  if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
954
1033
  const { id: id2, name } = config;
955
- if (typeof id2 !== "string" || !SLUG.test(id2)) {
1034
+ if (typeof id2 !== "string" || !SLUG2.test(id2)) {
956
1035
  throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id2)}`);
957
1036
  }
958
1037
  if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
@@ -977,7 +1056,7 @@ function defineChapter(config) {
977
1056
  const copy = resolveChapterCopy(config.copy);
978
1057
  const network = resolveNetwork(config, crm);
979
1058
  const formation = resolveLeaderFormation(config.formation, crm);
980
- const { schema, rules } = chapterDb(mode, auth);
1059
+ const { schema, rules } = chapterDb(mode, auth, network.targets.length > 0);
981
1060
  const services = config.services ?? ["db", "calendar", "o11y"];
982
1061
  const account = config.account ?? "none";
983
1062
  if (account !== "invite" && account !== "create" && account !== "none") {
@@ -1233,6 +1312,11 @@ function createChapterIntegration(chapter, options = {}) {
1233
1312
  // The snapshot route is deliberately private. A credential-free smoke
1234
1313
  // request must prove that the route is mounted and closed.
1235
1314
  { path: "/api/network/snapshot", expectedStatus: 401 },
1315
+ ...chapter.network.readers.length > 0 ? [{ path: "/api/network/records", expectedStatus: 401 }] : [],
1316
+ ...chapter.network.readers.some((reader) => reader.sharedNotes.length > 0) ? [{
1317
+ path: "/api/network/shared-notes",
1318
+ expectedStatus: 401
1319
+ }] : [],
1236
1320
  // Formation metadata is public only when the host explicitly enables
1237
1321
  // the bounded intake contract.
1238
1322
  ...chapter.formation.enabled ? [{ path: "/api/formation/config", expectedStatus: 200 }] : []