@odla-ai/chapter 0.26.0 → 0.27.0

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"> & {
@@ -70,17 +70,41 @@ interface ChapterNetworkTarget {
70
70
  name?: string;
71
71
  /** Absolute follower origin, e.g. `https://chapter.example.com`. */
72
72
  url: string;
73
+ /** Optional Cloudflare service-binding name for Worker-to-Worker delivery.
74
+ * The URL still defines the signed request origin; the binding only supplies
75
+ * the transport when a `workers.dev` subrequest cannot enter another Worker. */
76
+ binding?: string;
73
77
  /** Leader-vault key holding this follower's share secret. Defaults to
74
78
  * `network_share_<id-with-underscores>`. */
75
79
  secretName?: string;
76
80
  /** Per-record-type field allowlist. If present, omitted types cannot be sent
77
81
  * to this target. Without it, package-safe person/company defaults apply. */
78
82
  fields?: Record<string, readonly string[]>;
79
- }
80
- /** Leader/follower network configuration. Followers need no code config to
81
- * 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. */
82
105
  interface ChapterNetwork {
83
106
  targets?: readonly ChapterNetworkTarget[];
107
+ readers?: readonly ChapterNetworkReader[];
84
108
  }
85
109
  /** Public chapter-formation intake backed by one CRM record type. The host owns
86
110
  * the branded form; Chapter owns the bounded, idempotent write contract. */
@@ -112,12 +136,21 @@ interface ResolvedNetworkTarget {
112
136
  id: string;
113
137
  name: string;
114
138
  url: string;
139
+ binding?: string;
115
140
  secretName: string;
116
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[];
117
149
  }
118
150
  /** Fully-resolved leader network configuration. */
119
151
  interface ResolvedNetwork {
120
152
  targets: readonly ResolvedNetworkTarget[];
153
+ readers: readonly ResolvedNetworkReader[];
121
154
  }
122
155
 
123
156
  /** Which feature profile a site runs. `chapter` is the full public member site
@@ -400,8 +433,8 @@ interface ChapterConfig {
400
433
  /** User-visible language for packaged Chapter surfaces. Recursively partial;
401
434
  * `defineChapter()` resolves every omitted leaf from the package defaults. */
402
435
  copy?: ChapterCopyInput;
403
- /** Leader follower delivery targets. Secret values stay in the leader
404
- * 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. */
405
438
  network?: ChapterNetwork;
406
439
  /** Enable a public, host-rendered formation application workflow. */
407
440
  formation?: LeaderFormationConfig;
@@ -584,9 +617,8 @@ interface ChapterIntegrationDescriptor {
584
617
  }>;
585
618
  /** Runbooks that bear on THIS chapter's role, so tooling can point at the
586
619
  * right procedure without the operator knowing it exists. A chapter declaring
587
- * `network.targets` is a leader hub; one declaring none is a follower site,
588
- * and the two follow different procedures for the same words ("connect",
589
- * "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). */
590
622
  runbooks: ChapterRunbookHints;
591
623
  }
592
624
  /** Which runbooks apply, and why. */
@@ -613,7 +645,7 @@ declare function createChapterIntegration(chapter: Chapter, options?: ChapterInt
613
645
  * read-only super-admin tier (default on for the `"claim"` ladder). A `"claim"`
614
646
  * chapter therefore emits the reference namespace set — `applications`,
615
647
  * `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */
616
- declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth): {
648
+ declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean): {
617
649
  schema: DbSchema;
618
650
  rules: DbRules;
619
651
  };
@@ -1065,6 +1097,52 @@ interface NetworkRollup {
1065
1097
  }>;
1066
1098
  targets: NetworkRollupTarget[];
1067
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
+ }
1068
1146
  interface NormalizedSharedRecord {
1069
1147
  version: 1 | 2;
1070
1148
  sourceId: string;
@@ -1631,4 +1709,4 @@ type ApplicationBookingPatch = {
1631
1709
  * already there (never backward). */
1632
1710
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1633
1711
 
1634
- 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"> & {
@@ -70,17 +70,41 @@ interface ChapterNetworkTarget {
70
70
  name?: string;
71
71
  /** Absolute follower origin, e.g. `https://chapter.example.com`. */
72
72
  url: string;
73
+ /** Optional Cloudflare service-binding name for Worker-to-Worker delivery.
74
+ * The URL still defines the signed request origin; the binding only supplies
75
+ * the transport when a `workers.dev` subrequest cannot enter another Worker. */
76
+ binding?: string;
73
77
  /** Leader-vault key holding this follower's share secret. Defaults to
74
78
  * `network_share_<id-with-underscores>`. */
75
79
  secretName?: string;
76
80
  /** Per-record-type field allowlist. If present, omitted types cannot be sent
77
81
  * to this target. Without it, package-safe person/company defaults apply. */
78
82
  fields?: Record<string, readonly string[]>;
79
- }
80
- /** Leader/follower network configuration. Followers need no code config to
81
- * 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. */
82
105
  interface ChapterNetwork {
83
106
  targets?: readonly ChapterNetworkTarget[];
107
+ readers?: readonly ChapterNetworkReader[];
84
108
  }
85
109
  /** Public chapter-formation intake backed by one CRM record type. The host owns
86
110
  * the branded form; Chapter owns the bounded, idempotent write contract. */
@@ -112,12 +136,21 @@ interface ResolvedNetworkTarget {
112
136
  id: string;
113
137
  name: string;
114
138
  url: string;
139
+ binding?: string;
115
140
  secretName: string;
116
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[];
117
149
  }
118
150
  /** Fully-resolved leader network configuration. */
119
151
  interface ResolvedNetwork {
120
152
  targets: readonly ResolvedNetworkTarget[];
153
+ readers: readonly ResolvedNetworkReader[];
121
154
  }
122
155
 
123
156
  /** Which feature profile a site runs. `chapter` is the full public member site
@@ -400,8 +433,8 @@ interface ChapterConfig {
400
433
  /** User-visible language for packaged Chapter surfaces. Recursively partial;
401
434
  * `defineChapter()` resolves every omitted leaf from the package defaults. */
402
435
  copy?: ChapterCopyInput;
403
- /** Leader follower delivery targets. Secret values stay in the leader
404
- * 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. */
405
438
  network?: ChapterNetwork;
406
439
  /** Enable a public, host-rendered formation application workflow. */
407
440
  formation?: LeaderFormationConfig;
@@ -584,9 +617,8 @@ interface ChapterIntegrationDescriptor {
584
617
  }>;
585
618
  /** Runbooks that bear on THIS chapter's role, so tooling can point at the
586
619
  * right procedure without the operator knowing it exists. A chapter declaring
587
- * `network.targets` is a leader hub; one declaring none is a follower site,
588
- * and the two follow different procedures for the same words ("connect",
589
- * "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). */
590
622
  runbooks: ChapterRunbookHints;
591
623
  }
592
624
  /** Which runbooks apply, and why. */
@@ -613,7 +645,7 @@ declare function createChapterIntegration(chapter: Chapter, options?: ChapterInt
613
645
  * read-only super-admin tier (default on for the `"claim"` ladder). A `"claim"`
614
646
  * chapter therefore emits the reference namespace set — `applications`,
615
647
  * `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */
616
- declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth): {
648
+ declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean): {
617
649
  schema: DbSchema;
618
650
  rules: DbRules;
619
651
  };
@@ -1065,6 +1097,52 @@ interface NetworkRollup {
1065
1097
  }>;
1066
1098
  targets: NetworkRollupTarget[];
1067
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
+ }
1068
1146
  interface NormalizedSharedRecord {
1069
1147
  version: 1 | 2;
1070
1148
  sourceId: string;
@@ -1631,4 +1709,4 @@ type ApplicationBookingPatch = {
1631
1709
  * already there (never backward). */
1632
1710
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1633
1711
 
1634
- 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,9 +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_]*$/;
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
+ }
886
909
  function resolveNetwork(config, crm) {
887
910
  const seen = /* @__PURE__ */ new Set();
888
911
  const targets = [];
@@ -928,23 +951,87 @@ function resolveNetwork(config, crm) {
928
951
  if (!/^[a-zA-Z][a-zA-Z0-9_-]{1,127}$/.test(secretName)) {
929
952
  throw new Error(`defineChapter.network.targets.${target.id}.secretName: must be a vault-key identifier`);
930
953
  }
954
+ const binding = target.binding?.trim();
955
+ if (binding && !BINDING.test(binding)) {
956
+ throw new Error(
957
+ `defineChapter.network.targets.${target.id}.binding: must be an uppercase Worker binding identifier`
958
+ );
959
+ }
960
+ const sharedNotes = target.sharedNotes ?? [];
961
+ validateSharedNotes({
962
+ path: `defineChapter.network.targets.${target.id}.sharedNotes`,
963
+ values: sharedNotes,
964
+ crm,
965
+ fields
966
+ });
931
967
  targets.push({
932
968
  id: target.id,
933
969
  name: target.name?.trim() || target.id,
934
970
  url: url.origin,
971
+ ...binding ? { binding } : {},
935
972
  secretName,
936
- ...fields ? { fields } : {}
973
+ ...fields ? { fields } : {},
974
+ sharedNotes
975
+ });
976
+ }
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
937
1020
  });
1021
+ readers.push({ id: reader.id, fields: reader.fields, sharedNotes });
938
1022
  }
939
- return { targets };
1023
+ return { targets, readers };
940
1024
  }
1025
+
1026
+ // src/config.ts
1027
+ var SLUG2 = /^[a-z0-9][a-z0-9-]{1,62}$/;
941
1028
  function isResolvedCrm(x) {
942
1029
  return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
943
1030
  }
944
1031
  function defineChapter(config) {
945
1032
  if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
946
1033
  const { id: id2, name } = config;
947
- if (typeof id2 !== "string" || !SLUG.test(id2)) {
1034
+ if (typeof id2 !== "string" || !SLUG2.test(id2)) {
948
1035
  throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id2)}`);
949
1036
  }
950
1037
  if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
@@ -969,7 +1056,7 @@ function defineChapter(config) {
969
1056
  const copy = resolveChapterCopy(config.copy);
970
1057
  const network = resolveNetwork(config, crm);
971
1058
  const formation = resolveLeaderFormation(config.formation, crm);
972
- const { schema, rules } = chapterDb(mode, auth);
1059
+ const { schema, rules } = chapterDb(mode, auth, network.targets.length > 0);
973
1060
  const services = config.services ?? ["db", "calendar", "o11y"];
974
1061
  const account = config.account ?? "none";
975
1062
  if (account !== "invite" && account !== "create" && account !== "none") {
@@ -1225,6 +1312,11 @@ function createChapterIntegration(chapter, options = {}) {
1225
1312
  // The snapshot route is deliberately private. A credential-free smoke
1226
1313
  // request must prove that the route is mounted and closed.
1227
1314
  { path: "/api/network/snapshot", expectedStatus: 401 },
1315
+ ...chapter.network.readers.length > 0 ? [{ path: "/api/network/records?type=person", expectedStatus: 401 }] : [],
1316
+ ...chapter.network.readers.some((reader) => reader.sharedNotes.length > 0) ? [{
1317
+ path: "/api/network/shared-notes?type=person&recordId=probe",
1318
+ expectedStatus: 401
1319
+ }] : [],
1228
1320
  // Formation metadata is public only when the host explicitly enables
1229
1321
  // the bounded intake contract.
1230
1322
  ...chapter.formation.enabled ? [{ path: "/api/formation/config", expectedStatus: 200 }] : []