@tellescope/schema 1.250.1 → 1.251.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/src/schema.ts CHANGED
@@ -90,7 +90,11 @@ import {
90
90
  AutomationAction,
91
91
  TimeTrackTimestamp,
92
92
  BelugaPharmacyMapping,
93
+ BelugaAutomationMappingEntry,
94
+ BelugaUpdateVisitPatientPreferenceItem,
93
95
  TimeTrack,
96
+ LinkedAccount,
97
+ LinkedAccountAccessEntry,
94
98
  } from "@tellescope/types-models"
95
99
 
96
100
  import {
@@ -261,6 +265,7 @@ import {
261
265
  exactMatchValidator,
262
266
  exactMatchValidatorOptional,
263
267
  listOfMongoIdStringValidatorOptionalOrEmptyOk,
268
+ linkedAccountAccessValidator,
264
269
  listOfStringsValidatorOptionalOrEmptyOk,
265
270
  stringValidatorOptionalEmptyOkay,
266
271
  analyticsQueryResultsValidator,
@@ -759,7 +764,7 @@ export type CustomActions = {
759
764
  { fieldId: string, enduserId?: string, selectedProductIds?: string[] },
760
765
  { customerId: string, clientSecret: string, publishableKey: string, stripeAccount: string, businessName: string, answerText?: string, isCheckout?: boolean }
761
766
  >,
762
- chargebee_details: CustomAction<{ fieldId: string, billingAddress?: { addressLineOne?: string, addressLineTwo?: string, city?: string, state?: string, zipCode?: string }, verify?: boolean }, { url?: string, hasPaymentMethod?: boolean }>,
767
+ chargebee_details: CustomAction<{ fieldId: string, enduserId?: string, billingAddress?: { addressLineOne?: string, addressLineTwo?: string, city?: string, state?: string, zipCode?: string }, verify?: boolean }, { url?: string, hasPaymentMethod?: boolean }>,
763
768
  generate_pdf: CustomAction<
764
769
  { id: string },
765
770
  { }
@@ -934,6 +939,9 @@ export type CustomActions = {
934
939
  consent: CustomAction<{ termsVersion: string, }, { user: User, authToken: string }>,
935
940
  get_users_for_groups: CustomAction<{ groups: string[] }, { userIds: string[] }>,
936
941
  play_phone_message: CustomAction<{ userId: string, message: string, enduserId?: string, journeyContext?: JourneyContext }, { }>,
942
+ get_linked_accounts: CustomAction<{}, { linkedAccounts: LinkedAccount[] }>,
943
+ switch_account: CustomAction<{ targetUserId: string }, { authToken: string, user: User }>,
944
+ request_linked_account_access: CustomAction<{ targetEmail: string }, { }>,
937
945
  },
938
946
  chat_rooms: {
939
947
  join_room: CustomAction<{ id: string }, { room: ChatRoom }>,
@@ -3558,7 +3566,73 @@ export const schema: SchemaV1 = build_schema({
3558
3566
 
3559
3567
  return "User organizationIds are readonly"
3560
3568
  }
3561
- }
3569
+ },
3570
+ {
3571
+ explanation: "linkedAccountAccess mutations are constrained to the owner with allowlisted transitions",
3572
+ evaluate: (record, _, session, method, { updates, original }) => {
3573
+ if (!updates || !('linkedAccountAccess' in updates)) return
3574
+ if (method === 'create') {
3575
+ if (updates.linkedAccountAccess && (updates.linkedAccountAccess as any[]).length > 0) {
3576
+ return "linkedAccountAccess cannot be set on user creation"
3577
+ }
3578
+ return
3579
+ }
3580
+
3581
+ // Grant management is reserved to the actor's own session. From a switched session
3582
+ // (session.actorUserId set), even targeting the proxy identity's own record is rejected —
3583
+ // otherwise A-as-B could self-approve other pending requests on B, or delete B's existing
3584
+ // grants and silently revoke other grantees.
3585
+ if ((session as any).actorUserId) {
3586
+ return "Cannot update linkedAccountAccess from a switched session"
3587
+ }
3588
+
3589
+ // self-update only — record carries the post-merge updated document; original is the prior state.
3590
+ const ownerId = (record as any)._id?.toString() ?? (original as any)?._id?.toString() ?? (original as any)?.id
3591
+ if (!(ownerId && ownerId === session.id)) {
3592
+ return "Only the account owner can update linkedAccountAccess"
3593
+ }
3594
+
3595
+ const oldEntries: LinkedAccountAccessEntry[] = (original as any)?.linkedAccountAccess ?? []
3596
+ const newEntries: LinkedAccountAccessEntry[] = (updates.linkedAccountAccess as any) ?? []
3597
+
3598
+ for (const newEntry of newEntries) {
3599
+ const oldMatch = oldEntries.find(e => e.userId === newEntry.userId)
3600
+ if (!oldMatch) {
3601
+ return "Cannot add entries to linkedAccountAccess via direct update; use request_linked_account_access"
3602
+ }
3603
+
3604
+ if (newEntry.email !== oldMatch.email) return "linkedAccountAccess entry email is immutable"
3605
+ if ((newEntry.fname ?? null) !== (oldMatch.fname ?? null)) return "linkedAccountAccess entry fname is immutable"
3606
+ if ((newEntry.lname ?? null) !== (oldMatch.lname ?? null)) return "linkedAccountAccess entry lname is immutable"
3607
+ if ((newEntry.orgName ?? null) !== (oldMatch.orgName ?? null)) return "linkedAccountAccess entry orgName is immutable"
3608
+ if (new Date(newEntry.createdAt).getTime() !== new Date(oldMatch.createdAt).getTime()) return "linkedAccountAccess entry createdAt is immutable"
3609
+ if (new Date(newEntry.requestExpiresAt).getTime() !== new Date(oldMatch.requestExpiresAt).getTime()) return "linkedAccountAccess entry requestExpiresAt is immutable"
3610
+
3611
+ if (newEntry.status !== oldMatch.status) {
3612
+ if (!(oldMatch.status === 'pending' && newEntry.status === 'accepted')) {
3613
+ return "linkedAccountAccess status can only transition from pending to accepted"
3614
+ }
3615
+ // Reject approval of an expired pending request — owner must wait for the requester
3616
+ // to re-issue. requestExpiresAt is immutable per the rule above; the only way for a
3617
+ // pending entry to refresh is request_linked_account_access replacing the expired entry.
3618
+ if (new Date(oldMatch.requestExpiresAt).getTime() < Date.now()) {
3619
+ return "linkedAccountAccess request has expired and cannot be approved; requester must re-request"
3620
+ }
3621
+ }
3622
+ }
3623
+
3624
+ return
3625
+ }
3626
+ },
3627
+ {
3628
+ explanation: "Legacy accountAccessGrantedTo field is no longer accepted",
3629
+ evaluate: (_, __, ___, ____, { updates }) => {
3630
+ if (updates && 'accountAccessGrantedTo' in updates) {
3631
+ return "accountAccessGrantedTo has been replaced by linkedAccountAccess"
3632
+ }
3633
+ return
3634
+ }
3635
+ },
3562
3636
  ],
3563
3637
  },
3564
3638
  defaultActions: {
@@ -3731,7 +3805,7 @@ export const schema: SchemaV1 = build_schema({
3731
3805
  name: 'Play Phone Message',
3732
3806
  path: '/users/play-phone-message',
3733
3807
  description: "Calls the user and plays a recorded message",
3734
- parameters: {
3808
+ parameters: {
3735
3809
  userId: { validator: mongoIdStringValidator, required: true },
3736
3810
  message: { validator: stringValidator5000, required: true },
3737
3811
  enduserId: { validator: mongoIdStringValidator },
@@ -3739,6 +3813,39 @@ export const schema: SchemaV1 = build_schema({
3739
3813
  },
3740
3814
  returns: { },
3741
3815
  },
3816
+ get_linked_accounts: {
3817
+ op: "custom", access: 'read', method: "get",
3818
+ name: 'Get Linked Accounts',
3819
+ path: '/users/linked-accounts',
3820
+ description: "Returns accounts that have granted access to the caller",
3821
+ parameters: { },
3822
+ returns: {
3823
+ linkedAccounts: { validator: objectAnyFieldsAnyValuesValidator as any, required: true },
3824
+ },
3825
+ },
3826
+ switch_account: {
3827
+ op: "custom", access: 'update', method: "post",
3828
+ name: 'Switch Account',
3829
+ path: '/users/switch-account',
3830
+ description: "Switches the current session to a target account that has granted access",
3831
+ parameters: {
3832
+ targetUserId: { validator: mongoIdStringRequired, required: true },
3833
+ },
3834
+ returns: {
3835
+ authToken: { validator: stringValidator, required: true },
3836
+ user: { validator: 'user' as any, required: true },
3837
+ },
3838
+ },
3839
+ request_linked_account_access: {
3840
+ op: "custom", access: 'update', method: "post",
3841
+ name: 'Request Linked Account Access',
3842
+ path: '/users/request-linked-account-access',
3843
+ description: "Requests linked-account access from another user identified by email; the target user must accept before the requester can switch into the account",
3844
+ parameters: {
3845
+ targetEmail: { validator: emailValidator, required: true },
3846
+ },
3847
+ returns: { },
3848
+ },
3742
3849
  },
3743
3850
  publicActions: {
3744
3851
  begin_sso: {
@@ -3852,6 +3959,7 @@ export const schema: SchemaV1 = build_schema({
3852
3959
  email: {
3853
3960
  validator: emailValidator,
3854
3961
  required: true,
3962
+ updatesDisabled: true,
3855
3963
  examples: ['test@tellescope.com'],
3856
3964
  redactions: ['enduser'],
3857
3965
  },
@@ -3978,6 +4086,7 @@ export const schema: SchemaV1 = build_schema({
3978
4086
  dashboardView: { validator: customDashboardViewValidator },
3979
4087
  hideFromCalendarView: { validator: booleanValidator },
3980
4088
  requireSSO: { validator: listOfStringsValidatorUniqueOptionalOrEmptyOkay },
4089
+ linkedAccountAccess: { validator: linkedAccountAccessValidator },
3981
4090
  }
3982
4091
  },
3983
4092
  templates: {
@@ -5146,6 +5255,7 @@ export const schema: SchemaV1 = build_schema({
5146
5255
  description: "Gets the relevant information for a Chargebee field",
5147
5256
  parameters: {
5148
5257
  fieldId: { validator: mongoIdStringValidator, required: true },
5258
+ enduserId: { validator: mongoIdStringValidator },
5149
5259
  billingAddress: {
5150
5260
  validator: objectValidator<{
5151
5261
  addressLineOne?: string,
@@ -7169,6 +7279,7 @@ export const schema: SchemaV1 = build_schema({
7169
7279
  canvasSyncPhoneConsent: { validator: booleanValidator },
7170
7280
  canvasStateToLocationId: { validator: objectAnyFieldsValidator(stringValidator100) },
7171
7281
  enforceMFA: { validator: booleanValidator },
7282
+ accountSwitchingEnabled: { validator: booleanValidator },
7172
7283
  replyToEnduserTransactionalEmails: { validator: emailValidator },
7173
7284
  customTermsOfService: { validator: stringValidator },
7174
7285
  customPrivacyPolicy: { validator: stringValidator },
@@ -7224,6 +7335,20 @@ export const schema: SchemaV1 = build_schema({
7224
7335
  summaryFormId: mongoIdStringOptional,
7225
7336
  }))
7226
7337
  },
7338
+ belugaAutomationMappings: {
7339
+ validator: listValidatorOptionalOrEmptyOk(objectValidator<BelugaAutomationMappingEntry>({
7340
+ enduserCondition: optionalAnyObjectValidator,
7341
+ patientPreferences: listValidator(objectValidator<BelugaUpdateVisitPatientPreferenceItem>({
7342
+ name: stringValidator,
7343
+ strength: stringValidator,
7344
+ refills: stringValidator,
7345
+ quantity: stringValidator,
7346
+ daysSupply: stringValidator,
7347
+ medId: stringValidator,
7348
+ })),
7349
+ pharmacyId: stringValidator,
7350
+ }))
7351
+ },
7227
7352
  },
7228
7353
  },
7229
7354
  databases: {
@@ -8585,6 +8710,7 @@ If a voicemail is left, it is indicated by recordingURI, transcription, or recor
8585
8710
  status: { validator: stringValidator },
8586
8711
  allergyNote: { validator: stringValidator1000 },
8587
8712
  protocol: { validator: stringValidator1000 },
8713
+ category: { validator: stringValidator250 },
8588
8714
  scriptSureDraft: {
8589
8715
  validator: optionalAnyObjectValidator,
8590
8716
  },
@@ -9732,7 +9858,7 @@ If a voicemail is left, it is indicated by recordingURI, transcription, or recor
9732
9858
  type: { validator: stringValidator100 }, // only used on creation
9733
9859
  maxTokens: { validator: positiveNumberValidator },
9734
9860
  conversationId: { validator: mongoIdStringValidator },
9735
- prompt: { validator: stringValidator25000 },
9861
+ prompt: { validator: stringValidator100000EmptyOkay },
9736
9862
  orchestrationId: { validator: stringValidatorOptional }, // optional ID to group multiple conversations as part of the same workflow
9737
9863
  },
9738
9864
  returns: {