@memberjunction/server 6.2.0-edge.4 → 6.2.0-edge.5

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.
Files changed (287) hide show
  1. package/README.md +20 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +91 -30
  4. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  5. package/dist/agentSessions/SessionJanitor.js +311 -81
  6. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  7. package/dist/agentSessions/SessionManager.d.ts +5 -1
  8. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  9. package/dist/agentSessions/SessionManager.js +13 -1
  10. package/dist/agentSessions/SessionManager.js.map +1 -1
  11. package/dist/apiRoot.d.ts +32 -0
  12. package/dist/apiRoot.d.ts.map +1 -0
  13. package/dist/apiRoot.js +67 -0
  14. package/dist/apiRoot.js.map +1 -0
  15. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  16. package/dist/auth/AuthProviderCatalogRouter.js +25 -0
  17. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  18. package/dist/auth/LoginScreenEngine.d.ts +22 -0
  19. package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
  20. package/dist/auth/LoginScreenEngine.js +57 -0
  21. package/dist/auth/LoginScreenEngine.js.map +1 -0
  22. package/dist/auth/index.d.ts +1 -0
  23. package/dist/auth/index.d.ts.map +1 -1
  24. package/dist/auth/index.js +1 -0
  25. package/dist/auth/index.js.map +1 -1
  26. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  27. package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
  28. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  29. package/dist/config.d.ts +136 -22
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +43 -4
  32. package/dist/config.js.map +1 -1
  33. package/dist/coreRoutes.d.ts +29 -0
  34. package/dist/coreRoutes.d.ts.map +1 -0
  35. package/dist/coreRoutes.js +142 -0
  36. package/dist/coreRoutes.js.map +1 -0
  37. package/dist/generated/graphql-schemas/__mj.d.ts +369 -3
  38. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  39. package/dist/generated/graphql-schemas/__mj.js +1750 -7
  40. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  41. package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
  42. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  43. package/dist/generic/CacheInvalidationResolver.js +59 -19
  44. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  45. package/dist/generic/PushStatusResolver.js +1 -1
  46. package/dist/generic/PushStatusResolver.js.map +1 -1
  47. package/dist/generic/ResolverBase.d.ts +103 -44
  48. package/dist/generic/ResolverBase.d.ts.map +1 -1
  49. package/dist/generic/ResolverBase.js +264 -305
  50. package/dist/generic/ResolverBase.js.map +1 -1
  51. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  52. package/dist/generic/RunViewResolver.js +12 -0
  53. package/dist/generic/RunViewResolver.js.map +1 -1
  54. package/dist/globalCors.d.ts +33 -0
  55. package/dist/globalCors.d.ts.map +1 -0
  56. package/dist/globalCors.js +58 -0
  57. package/dist/globalCors.js.map +1 -0
  58. package/dist/index.d.ts +11 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +112 -160
  61. package/dist/index.js.map +1 -1
  62. package/dist/multiTenancy/index.d.ts.map +1 -1
  63. package/dist/multiTenancy/index.js +22 -2
  64. package/dist/multiTenancy/index.js.map +1 -1
  65. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -1
  66. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +2 -1
  67. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -1
  68. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -1
  69. package/dist/realtimeSessions/RealtimeVerifyRouter.js +2 -1
  70. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -1
  71. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
  72. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
  73. package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
  74. package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
  75. package/dist/realtimeSessions/index.d.ts +2 -0
  76. package/dist/realtimeSessions/index.d.ts.map +1 -1
  77. package/dist/realtimeSessions/index.js +2 -0
  78. package/dist/realtimeSessions/index.js.map +1 -1
  79. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
  80. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
  81. package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
  82. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
  83. package/dist/realtimeSessions/sessionAccess.d.ts +6 -0
  84. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  85. package/dist/realtimeSessions/sessionAccess.js +7 -3
  86. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  87. package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
  88. package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
  89. package/dist/resolvers/ArtifactFileResolver.js +9 -7
  90. package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
  91. package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
  92. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  93. package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
  94. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  95. package/dist/resolvers/DatasetResolver.d.ts +24 -3
  96. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  97. package/dist/resolvers/DatasetResolver.js +57 -8
  98. package/dist/resolvers/DatasetResolver.js.map +1 -1
  99. package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
  100. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  101. package/dist/resolvers/EntityRecordNameResolver.js +3 -2
  102. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  103. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +18 -3
  104. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  105. package/dist/resolvers/FetchEntityVectorsResolver.js +100 -35
  106. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  107. package/dist/resolvers/FileResolver.d.ts +82 -11
  108. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  109. package/dist/resolvers/FileResolver.js +279 -101
  110. package/dist/resolvers/FileResolver.js.map +1 -1
  111. package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
  112. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  113. package/dist/resolvers/GetDataContextDataResolver.js +117 -38
  114. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  115. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  116. package/dist/resolvers/GetDataResolver.js +6 -3
  117. package/dist/resolvers/GetDataResolver.js.map +1 -1
  118. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  119. package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
  120. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  121. package/dist/resolvers/QueryResolver.d.ts +2 -0
  122. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  123. package/dist/resolvers/QueryResolver.js +13 -0
  124. package/dist/resolvers/QueryResolver.js.map +1 -1
  125. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +2 -8
  126. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  127. package/dist/resolvers/RealtimeClientSessionResolver.js +10 -26
  128. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  129. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  130. package/dist/resolvers/RunAIAgentResolver.js +35 -6
  131. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  132. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
  133. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
  134. package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
  135. package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
  136. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  137. package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
  138. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  139. package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
  140. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  141. package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
  142. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  143. package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
  144. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  145. package/dist/resolvers/TestQuerySQLResolver.js +65 -13
  146. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  147. package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
  148. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  149. package/dist/resolvers/TransactionGroupResolver.js +102 -24
  150. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  151. package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
  152. package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
  153. package/dist/resolvers/agent-run-data-guard.js +72 -0
  154. package/dist/resolvers/agent-run-data-guard.js.map +1 -0
  155. package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
  156. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
  157. package/dist/rest/RealtimeRecordingHandler.js +383 -0
  158. package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
  159. package/dist/rest/ViewOperationsHandler.d.ts +3 -1
  160. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  161. package/dist/rest/ViewOperationsHandler.js +21 -12
  162. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  163. package/dist/rest/setupRESTEndpoints.d.ts +6 -0
  164. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  165. package/dist/rest/setupRESTEndpoints.js +6 -0
  166. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  167. package/dist/serverExtensionReservedRoots.d.ts +12 -10
  168. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  169. package/dist/serverExtensionReservedRoots.js +14 -17
  170. package/dist/serverExtensionReservedRoots.js.map +1 -1
  171. package/dist/services/MJStorageBlobStore.d.ts +8 -1
  172. package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
  173. package/dist/services/MJStorageBlobStore.js +25 -19
  174. package/dist/services/MJStorageBlobStore.js.map +1 -1
  175. package/dist/services/StartTaskGraphDispatcher.js +2 -2
  176. package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
  177. package/dist/services/TaskGraphActionRunner.d.ts +5 -3
  178. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  179. package/dist/services/TaskGraphActionRunner.js +27 -5
  180. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  181. package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
  182. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  183. package/dist/services/TaskGraphAgentRunner.js +157 -9
  184. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  185. package/dist/services/TaskGraphContinuationDeliverer.d.ts +54 -2
  186. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  187. package/dist/services/TaskGraphContinuationDeliverer.js +598 -15
  188. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  189. package/dist/sharedCache.d.ts.map +1 -1
  190. package/dist/sharedCache.js +3 -0
  191. package/dist/sharedCache.js.map +1 -1
  192. package/package.json +99 -98
  193. package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
  194. package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
  195. package/src/__tests__/FetchEntityVectorsResolver.test.ts +163 -15
  196. package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
  197. package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
  198. package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
  199. package/src/__tests__/LoginScreenEngine.test.ts +86 -0
  200. package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
  201. package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
  202. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
  203. package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
  204. package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
  205. package/src/__tests__/ResolverBase.clientOldValues.test.ts +209 -6
  206. package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
  207. package/src/__tests__/RunAIAgentResolver.branch.test.ts +1 -0
  208. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +5 -3
  209. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
  210. package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
  211. package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
  212. package/src/__tests__/SessionJanitor.test.ts +365 -10
  213. package/src/__tests__/SessionManager.test.ts +27 -0
  214. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
  215. package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
  216. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +356 -22
  217. package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
  218. package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
  219. package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
  220. package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
  221. package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
  222. package/src/__tests__/agent-run-data-guard.test.ts +130 -0
  223. package/src/__tests__/apiRootBoot.test.ts +202 -0
  224. package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
  225. package/src/__tests__/conversationRunEvents.test.ts +56 -14
  226. package/src/__tests__/globalCors.test.ts +217 -0
  227. package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
  228. package/src/__tests__/multiTenancy.security.test.ts +51 -0
  229. package/src/__tests__/multiTenancy.test.ts +1 -1
  230. package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
  231. package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
  232. package/src/__tests__/realtimeRecordingHandler.test.ts +652 -0
  233. package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
  234. package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
  235. package/src/__tests__/sharedCache.test.ts +5 -3
  236. package/src/agentSessions/SessionJanitor.ts +369 -93
  237. package/src/agentSessions/SessionManager.ts +13 -1
  238. package/src/apiRoot.ts +78 -0
  239. package/src/auth/AuthProviderCatalogRouter.ts +26 -0
  240. package/src/auth/LoginScreenEngine.ts +50 -0
  241. package/src/auth/index.ts +1 -0
  242. package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
  243. package/src/config.ts +46 -5
  244. package/src/coreRoutes.ts +166 -0
  245. package/src/generated/graphql-schemas/__mj.ts +1273 -10
  246. package/src/generic/CacheInvalidationResolver.ts +73 -18
  247. package/src/generic/PushStatusResolver.ts +1 -1
  248. package/src/generic/ResolverBase.ts +304 -325
  249. package/src/generic/RunViewResolver.ts +17 -0
  250. package/src/globalCors.ts +71 -0
  251. package/src/index.ts +116 -174
  252. package/src/multiTenancy/index.ts +24 -2
  253. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
  254. package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
  255. package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
  256. package/src/realtimeSessions/index.ts +2 -0
  257. package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
  258. package/src/realtimeSessions/sessionAccess.ts +7 -3
  259. package/src/resolvers/ArtifactFileResolver.ts +9 -8
  260. package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
  261. package/src/resolvers/DatasetResolver.ts +70 -10
  262. package/src/resolvers/EntityRecordNameResolver.ts +3 -2
  263. package/src/resolvers/FetchEntityVectorsResolver.ts +121 -39
  264. package/src/resolvers/FileResolver.ts +321 -106
  265. package/src/resolvers/GetDataContextDataResolver.ts +139 -41
  266. package/src/resolvers/GetDataResolver.ts +6 -3
  267. package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
  268. package/src/resolvers/QueryResolver.ts +11 -0
  269. package/src/resolvers/RealtimeClientSessionResolver.ts +10 -33
  270. package/src/resolvers/RunAIAgentResolver.ts +39 -4
  271. package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
  272. package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
  273. package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
  274. package/src/resolvers/TestQuerySQLResolver.ts +68 -13
  275. package/src/resolvers/TransactionGroupResolver.ts +131 -27
  276. package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
  277. package/src/resolvers/agent-run-data-guard.ts +89 -0
  278. package/src/rest/RealtimeRecordingHandler.ts +464 -0
  279. package/src/rest/ViewOperationsHandler.ts +29 -13
  280. package/src/rest/setupRESTEndpoints.ts +6 -0
  281. package/src/serverExtensionReservedRoots.ts +14 -17
  282. package/src/services/MJStorageBlobStore.ts +31 -30
  283. package/src/services/StartTaskGraphDispatcher.ts +2 -2
  284. package/src/services/TaskGraphActionRunner.ts +25 -5
  285. package/src/services/TaskGraphAgentRunner.ts +188 -11
  286. package/src/services/TaskGraphContinuationDeliverer.ts +704 -14
  287. package/src/sharedCache.ts +3 -0
@@ -19,6 +19,7 @@ import { Arg, Field, ID, ObjectType, PubSubEngine, Resolver, ResolverFilterData,
19
19
  import { LogError } from '@memberjunction/core';
20
20
  import { UUIDsEqual } from '@memberjunction/global';
21
21
  import type { TaskGraphFrame, TaskGraphObserver } from '@memberjunction/task-graph';
22
+ import { PublishStatusUpdate } from '../generic/PushStatusResolver.js';
22
23
 
23
24
  export const TASK_GRAPH_FRAMES_TOPIC = 'TASK_GRAPH_FRAMES';
24
25
 
@@ -173,7 +174,10 @@ export function taskGraphFrameFilter(data: {
173
174
  * the same reason.
174
175
  */
175
176
  export class TaskGraphFrameBroadcaster implements TaskGraphObserver {
176
- constructor(private readonly pubSub: PubSubEngine) {}
177
+ constructor(
178
+ private readonly pubSub: PubSubEngine,
179
+ private readonly bridgeToPushStatus: boolean = false,
180
+ ) {}
177
181
 
178
182
  public OnFrame(frame: TaskGraphFrame): void {
179
183
  try {
@@ -187,6 +191,34 @@ export class TaskGraphFrameBroadcaster implements TaskGraphObserver {
187
191
  TASK_GRAPH_FRAMES_TOPIC,
188
192
  { ...frame, ownerUserId: frame.OwnerUserID } satisfies TaskGraphFramePayload,
189
193
  );
194
+
195
+ if (
196
+ this.bridgeToPushStatus &&
197
+ (frame.Kind === 'TaskStarted' ||
198
+ frame.Kind === 'TaskCompleted' ||
199
+ frame.Kind === 'TaskFailed' ||
200
+ frame.Kind === 'NodeProgress' ||
201
+ frame.Kind === 'GraphSettled')
202
+ ) {
203
+ const defaultMsg = frame.TaskName
204
+ ? `${frame.TaskName}: ${frame.Status ?? frame.Kind}`
205
+ : `Workflow: ${frame.Status ?? frame.Kind}`;
206
+ PublishStatusUpdate(this.pubSub, {
207
+ sessionId: '*',
208
+ ownerUserId: frame.OwnerUserID,
209
+ message: JSON.stringify({
210
+ resolver: 'TaskOrchestrator',
211
+ type: 'TaskGraphFrame',
212
+ status: 'ok',
213
+ data: {
214
+ ...frame,
215
+ taskName: frame.TaskName,
216
+ message: frame.ProgressMessage || defaultMsg,
217
+ percentComplete: frame.ProgressPercent,
218
+ },
219
+ }),
220
+ });
221
+ }
190
222
  } catch (e) {
191
223
  LogError(`[TaskGraphFrameBroadcaster] Could not publish ${frame.Kind} (ignored): ${e instanceof Error ? e.message : String(e)}`);
192
224
  }
@@ -1,9 +1,12 @@
1
1
  import { Arg, Ctx, Field, InputType, Int, ObjectType, Query, Resolver } from 'type-graphql';
2
2
  import { GraphQLJSONObject } from 'graphql-type-json';
3
3
  import { RunQuery, IRunQueryProvider, LogError, LogStatus, QueryExecutionSpec } from '@memberjunction/core';
4
+ import type { DatabaseProviderBase, UserInfo } from '@memberjunction/core';
5
+ import { RenderPipeline } from '@memberjunction/generic-database-provider';
4
6
  import { AppContext } from '../types.js';
5
7
  import { GetReadOnlyProvider } from '../util.js';
6
8
  import { ResolverBase } from '../generic/ResolverBase.js';
9
+ import { IsScopeLimitedPrincipal } from '../auth/scopeLimitedPrincipal.js';
7
10
 
8
11
  /**
9
12
  * GraphQL input type for inline dependency queries.
@@ -88,6 +91,14 @@ export class TestQuerySQLResult {
88
91
  *
89
92
  * Security:
90
93
  * - Requires `query:test` scope for API key users (no-op for JWT auth)
94
+ * - Refuses scope-limited principals (see {@link IsScopeLimitedPrincipal}). Raw SQL bypasses
95
+ * RunView, entity permissions and row-level security, so the RLS scope tokens that confine a
96
+ * magic-link session are never applied on this path — there is no narrower filter to fall back
97
+ * to, only refusal.
98
+ * - Authorizes the RENDERED SQL: every table reference must be an entity base view the caller
99
+ * reads unscoped (CanRead, no row-level filter, no denied fields). Entities with row/field
100
+ * policy for this user are refused — raw SQL cannot apply that policy, so those reads belong
101
+ * on RunView. Same screen as {@link AdhocQueryResolver.ExecuteAdhocQuery}.
91
102
  * - Uses read-only database connection (no mutation possible)
92
103
  * - Enforces MaxRows limit (default 100) to prevent unbounded queries
93
104
  *
@@ -104,15 +115,24 @@ export class TestQuerySQLResolver extends ResolverBase {
104
115
  ): Promise<TestQuerySQLResult> {
105
116
  try {
106
117
  await this.CheckAPIKeyScopeAuthorization('query:test', '*', context.userPayload);
118
+ // SECURITY: refuse principals whose read authority is narrower than their role grant.
119
+ // Transient test SQL does not go through RunView, entity permissions or row-level
120
+ // security, so the RLS scope tokens that confine a magic-link session are never
121
+ // substituted. There is no filter to narrow, so the only correct answer for such a
122
+ // principal is no — the same refusal ExecuteAdhocQuery applies.
123
+ if (IsScopeLimitedPrincipal(context.userPayload?.userRecord)) {
124
+ return this.buildErrorResult('Ad-hoc SQL execution is not permitted for scope-limited sessions.');
125
+ }
107
126
  // Use read-only provider for security — no mutation possible
108
127
  const provider = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: false });
109
128
  if (!provider) {
110
- return {
111
- Success: false,
112
- RowCount: 0,
113
- ExecutionTime: 0,
114
- ErrorMessage: 'Read-only data source is not available. TestQuerySQL requires a read-only connection for security.',
115
- };
129
+ return this.buildErrorResult('Read-only data source is not available. TestQuerySQL requires a read-only connection for security.');
130
+ }
131
+
132
+ const contextUser = context.userPayload?.userRecord;
133
+ const refusal = this.AuthorizeRenderedTestSQL(input, provider, contextUser);
134
+ if (refusal) {
135
+ return this.buildErrorResult(refusal);
116
136
  }
117
137
 
118
138
  const rq = new RunQuery(provider as unknown as IRunQueryProvider);
@@ -127,7 +147,7 @@ export class TestQuerySQLResolver extends ResolverBase {
127
147
 
128
148
  LogStatus(`TestQuerySQL: Executing transient query (MaxRows=${spec.MaxRows}, UsesTemplate=${spec.UsesTemplate ?? false}, Dependencies=${spec.Dependencies?.length ?? 0})`);
129
149
 
130
- const result = await rq.ExecuteFromSpec(spec, context.userPayload.userRecord);
150
+ const result = await rq.ExecuteFromSpec(spec, contextUser);
131
151
 
132
152
  return {
133
153
  Success: result.Success,
@@ -141,12 +161,47 @@ export class TestQuerySQLResolver extends ResolverBase {
141
161
  } catch (err) {
142
162
  LogError(err);
143
163
  const errorMessage = err instanceof Error ? err.message : String(err);
144
- return {
145
- Success: false,
146
- RowCount: 0,
147
- ExecutionTime: 0,
148
- ErrorMessage: `TestQuerySQL failed: ${errorMessage}`,
149
- };
164
+ return this.buildErrorResult(`TestQuerySQL failed: ${errorMessage}`);
150
165
  }
151
166
  }
167
+
168
+ /**
169
+ * SECURITY: authorizes the READ itself, against the SQL that will actually run — the same
170
+ * screen `AdhocQueryResolver.AuthorizeRenderedSQL` applies to ad-hoc SQL. Rendering comes
171
+ * first because composition (`{{query:"..."}}`), inline dependencies and Nunjucks templates
172
+ * can introduce table references the raw input never named, so the render here uses the same
173
+ * Parameters / UsesTemplate / Dependencies the execution spec will. Every table reference in
174
+ * the rendered SQL must be an entity base view the caller can read UNSCOPED — CanRead with no
175
+ * row-level filter and no denied fields — because raw SQL applies neither (see
176
+ * `assertFullQueryUsesReadableEntityViews`). The provider renders the same input the same way
177
+ * and only wraps it in server-built MaxRows capping, so the rendered SQL is the full set of
178
+ * reads. Returns the refusal message, or null.
179
+ */
180
+ public AuthorizeRenderedTestSQL(input: TestQuerySQLInput, provider: DatabaseProviderBase, contextUser: UserInfo | undefined): string | null {
181
+ try {
182
+ const rendered = RenderPipeline.Run(input.SQL, {
183
+ Platform: provider.PlatformKey,
184
+ ContextUser: contextUser,
185
+ Parameters: input.Parameters,
186
+ UsesTemplate: input.UsesTemplate,
187
+ Dependencies: input.Dependencies,
188
+ OriginalSQL: input.SQL,
189
+ RequireReadStatement: true,
190
+ });
191
+ this.assertFullQueryUsesReadableEntityViews(rendered.Trace.AfterTemplates, provider, contextUser, 'transient test SQL');
192
+ return null;
193
+ } catch (err) {
194
+ return err instanceof Error ? err.message : String(err);
195
+ }
196
+ }
197
+
198
+ /** Builds a failed TestQuerySQLResult carrying the given message. */
199
+ private buildErrorResult(errorMessage: string): TestQuerySQLResult {
200
+ return {
201
+ Success: false,
202
+ RowCount: 0,
203
+ ExecutionTime: 0,
204
+ ErrorMessage: errorMessage,
205
+ };
206
+ }
152
207
  }
@@ -1,7 +1,7 @@
1
1
  import { Arg, Ctx, Field, InputType, Int, Mutation, ObjectType, registerEnumType } from 'type-graphql';
2
2
  import { AppContext } from '../types.js';
3
- import { CompositeKey, IMetadataProvider, KeyValuePair, LogError, Metadata, TransactionVariable, BaseEntity, EntityDeleteOptions, EntitySaveOptions } from '@memberjunction/core';
4
- import { SafeJSONParse } from '@memberjunction/global';
3
+ import { CompositeKey, EntityInfo, IMetadataProvider, KeyValuePair, LogError, Metadata, TransactionVariable, TransactionGroupBase, BaseEntity, EntityDeleteOptions, EntitySaveOptions } from '@memberjunction/core';
4
+ import { SafeJSONParse, ValuesEqual } from '@memberjunction/global';
5
5
  import { GetReadWriteProvider } from '../util.js';
6
6
  import { ResolverBase } from '../generic/ResolverBase.js';
7
7
 
@@ -142,11 +142,19 @@ export class TransactionResolver extends ResolverBase {
142
142
  // rows were all refused arrive at Submit() empty, take its legitimate "nothing to do"
143
143
  // branch, and report success for writes that never happened.
144
144
  const refusals: string[] = [];
145
+ const preUpdateValues = new Map<number, Record<string, unknown>>();
146
+
147
+ // instantiate an entity object for every item first, so the group's variables are known
148
+ // BEFORE any Save()/Delete() runs below: a 'Use' variable sets its field at Submit(), after
149
+ // every server-side write check has passed, so an entity whose check depends on a field's
150
+ // final value must be able to see that a variable will set it.
151
+ for (const item of group.Items) {
152
+ entityObjects.push(await md.GetEntityObject(item.EntityName, context.userPayload.userRecord));
153
+ }
154
+ this.AddNetworkVariables(group, entityObjects, tg);
145
155
 
146
156
  for (const [index, item] of group.Items.entries()) {
147
- // instantiate a new entity object for the item
148
- const entity = await md.GetEntityObject(item.EntityName, context.userPayload.userRecord);
149
- entityObjects.push(entity); // save for later for mapping variables if needed
157
+ const entity = entityObjects[index];
150
158
 
151
159
  // get the values from the payload
152
160
  const itemValues = SafeJSONParse(item.EntityObjectJSON);
@@ -162,6 +170,11 @@ export class TransactionResolver extends ResolverBase {
162
170
  case "Create":
163
171
  if (item.OperationType === "Update") {
164
172
  await entity.InnerLoad(pkey);
173
+ if (typeof entity.GetDataObject === 'function') {
174
+ preUpdateValues.set(index, await entity.GetDataObject());
175
+ } else if (typeof entity.GetAll === 'function') {
176
+ preUpdateValues.set(index, { ...(entity.GetAll() as Record<string, unknown>) });
177
+ }
165
178
  }
166
179
  objectValues.push(itemValues);
167
180
  entity.SetMany(itemValues, true);
@@ -208,32 +221,17 @@ export class TransactionResolver extends ResolverBase {
208
221
  `TransactionResolver::ExecuteTransactionGroup --- ${refusals.length} of ${group.Items.length} ` +
209
222
  `item(s) were refused, so the group was not submitted:\n${refusals.join('\n')}`
210
223
  );
211
- return await this.PrepareReturnValue(false, entityObjects, objectValues, group);
212
- }
213
-
214
- // now, we need to set the variables
215
- if (group.Variables && group.Variables.length > 0) {
216
- for (const networkVar of group.Variables) {
217
- // for each variable, add it to the transaction group and map the index from the network payload to the specific entity object loaded up above
218
- if (networkVar.ItemIndex >= 0 && networkVar.ItemIndex < entityObjects.length) {
219
- const entityObject = entityObjects[networkVar.ItemIndex];
220
- const newVar = new TransactionVariable(networkVar.Name, entityObject, networkVar.FieldName, networkVar.Type);
221
- tg.AddVariable(newVar);
222
- }
223
- else {
224
- throw new Error(`TransactionResolver::ExecuteTransactionGroup --- Error\n\n' + 'Invalid ItemIndex ${networkVar.ItemIndex} in TransactionVariable "${JSON.stringify(networkVar)}"`);
225
- }
226
- }
224
+ return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues);
227
225
  }
228
226
 
229
227
  // after all that, we are ready to roll, so let's run the TG
230
228
  if (await tg.Submit()) {
231
229
  // success!
232
- return await this.PrepareReturnValue(true, entityObjects, objectValues, group);
230
+ return await this.PrepareReturnValue(true, entityObjects, objectValues, group, preUpdateValues);
233
231
  }
234
232
  else {
235
233
  // failure, send back the results
236
- return await this.PrepareReturnValue(false, entityObjects, objectValues, group);
234
+ return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues);
237
235
  }
238
236
  }
239
237
  catch (err) {
@@ -242,15 +240,48 @@ export class TransactionResolver extends ResolverBase {
242
240
  }
243
241
  }
244
242
 
245
- protected async PrepareReturnValue(success: boolean, entityObjects: BaseEntity[], objectValues: any[], group: TransactionInputType): Promise<TransactionOutputType> {
243
+ /**
244
+ * Adds the network payload's variables to the group, mapping each one's item index to the entity
245
+ * object built for that item.
246
+ */
247
+ protected AddNetworkVariables(group: TransactionInputType, entityObjects: BaseEntity[], tg: TransactionGroupBase): void {
248
+ for (const networkVar of group.Variables ?? []) {
249
+ if (networkVar.ItemIndex >= 0 && networkVar.ItemIndex < entityObjects.length) {
250
+ const entityObject = entityObjects[networkVar.ItemIndex];
251
+ tg.AddVariable(new TransactionVariable(networkVar.Name, entityObject, networkVar.FieldName, networkVar.Type));
252
+ }
253
+ else {
254
+ throw new Error(`TransactionResolver::ExecuteTransactionGroup --- Error\n\n' + 'Invalid ItemIndex ${networkVar.ItemIndex} in TransactionVariable "${JSON.stringify(networkVar)}"`);
255
+ }
256
+ }
257
+ }
258
+
259
+ protected async PrepareReturnValue(
260
+ success: boolean,
261
+ entityObjects: BaseEntity[],
262
+ objectValues: unknown[],
263
+ group: TransactionInputType,
264
+ preUpdateValues?: Map<number, Record<string, unknown>>
265
+ ): Promise<TransactionOutputType> {
246
266
  const jsonResults = [];
247
267
  for (let i = 0; i < group.Items.length; i++) {
248
268
  const item = group.Items[i];
249
- if (item.OperationType==='Delete') {
269
+ if (item.OperationType === 'Delete') {
250
270
  jsonResults.push(JSON.stringify(objectValues[i]));
251
271
  }
272
+ else if (item.OperationType === 'Update') {
273
+ jsonResults.push(
274
+ await this.serializeUpdateReturnValue(
275
+ entityObjects[i],
276
+ objectValues[i],
277
+ preUpdateValues?.get(i),
278
+ group,
279
+ i
280
+ )
281
+ );
282
+ }
252
283
  else {
253
- // create or update, return what is in the database
284
+ // create, return what is in the database
254
285
  jsonResults.push(await entityObjects[i].GetDataObjectJSON());
255
286
  }
256
287
  }
@@ -261,6 +292,79 @@ export class TransactionResolver extends ResolverBase {
261
292
  ResultsJSON: jsonResults
262
293
  }
263
294
  }
264
-
295
+
296
+ /**
297
+ * Serializes a delta return payload for an `Update` transaction group item containing:
298
+ * - Primary key fields
299
+ * - All keys sent by the client in `sentValues` (`objectValues[i]`)
300
+ * - Any `TransactionVariable` fields targeting this item
301
+ * - `__mj_CreatedAt` and `__mj_UpdatedAt`
302
+ * - Virtual fields when any foreign key field was in `sentValues`
303
+ * - Any field whose post-save value differs from its pre-update DB snapshot (`preSaveValues`),
304
+ * capturing virtual fields, triggers, or server-side `BeforeSave()` mutations automatically
305
+ * while omitting unchanged columns.
306
+ */
307
+ private async serializeUpdateReturnValue(
308
+ entity: BaseEntity,
309
+ sentValues: unknown,
310
+ preSaveValues: Record<string, unknown> | undefined,
311
+ group: TransactionInputType,
312
+ itemIndex: number
313
+ ): Promise<string> {
314
+ const fullDataObj: Record<string, unknown> | null = typeof entity.GetDataObject === 'function'
315
+ ? (await entity.GetDataObject() as Record<string, unknown>)
316
+ : SafeJSONParse<Record<string, unknown>>(await entity.GetDataObjectJSON());
317
+ if (!fullDataObj || typeof fullDataObj !== 'object' || !sentValues || typeof sentValues !== 'object') {
318
+ return await entity.GetDataObjectJSON();
319
+ }
320
+
321
+ const sentRecord = sentValues as Record<string, unknown>;
322
+ const includeKeys = new Set<string>([
323
+ EntityInfo.CreatedAtFieldName,
324
+ EntityInfo.UpdatedAtFieldName,
325
+ ]);
326
+
327
+ for (const pk of entity.PrimaryKeys ?? []) {
328
+ if (pk?.Name) {
329
+ includeKeys.add(pk.Name);
330
+ }
331
+ }
332
+ for (const key of Object.keys(sentRecord)) {
333
+ includeKeys.add(key);
334
+ }
335
+ for (const v of group.Variables ?? []) {
336
+ if (v.ItemIndex === itemIndex && v.FieldName) {
337
+ includeKeys.add(v.FieldName);
338
+ }
339
+ }
340
+ if (Array.isArray(entity.Fields)) {
341
+ for (const f of entity.Fields) {
342
+ if (
343
+ f.EntityFieldInfo?.IsSpecialDateField ||
344
+ Boolean(f.EntityFieldInfo?.IsVirtual) ||
345
+ Boolean(f.EntityFieldInfo?.IsComputed) ||
346
+ f.ReadOnly
347
+ ) {
348
+ includeKeys.add(f.Name);
349
+ }
350
+ }
351
+ }
352
+ if (preSaveValues) {
353
+ for (const key of Object.keys(fullDataObj)) {
354
+ if (!ValuesEqual(fullDataObj[key], preSaveValues[key])) {
355
+ includeKeys.add(key);
356
+ }
357
+ }
358
+ }
359
+
360
+ const deltaObj: Record<string, unknown> = {};
361
+ for (const key of Object.keys(fullDataObj)) {
362
+ if (includeKeys.has(key)) {
363
+ deltaObj[key] = fullDataObj[key];
364
+ }
365
+ }
366
+ return JSON.stringify(deltaObj);
367
+ }
368
+
265
369
  }
266
370
 
@@ -0,0 +1,95 @@
1
+ // type-graphql decorators on the resolver class need the reflect-metadata polyfill.
2
+ import 'reflect-metadata';
3
+
4
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
5
+ import type { MockInstance } from 'vitest';
6
+ import { SQLExpressionValidator } from '@memberjunction/global';
7
+
8
+ const { executedSQL } = vi.hoisted(() => ({ executedSQL: [] as string[] }));
9
+
10
+ // GetData's server bootstrap, provider, auth and driver imports are replaced so the resolver runs
11
+ // offline. The fake driver records every text it is asked to run.
12
+ vi.mock('../../index.js', () => ({ GetDbType: () => 'sqlserver' }));
13
+ vi.mock('@memberjunction/generic-database-provider', () => ({
14
+ QueryCompositionEngine: class {
15
+ HasCompositionTokens(): boolean {
16
+ return false;
17
+ }
18
+ },
19
+ }));
20
+ vi.mock('../../util.js', () => ({ GetReadOnlyDataSource: () => ({}), GetReadOnlyProvider: () => null }));
21
+ vi.mock('../../auth/index.js', () => ({ GetSystemUser: async () => ({}) }));
22
+ vi.mock('mssql', () => ({
23
+ default: {
24
+ Request: class {
25
+ async query(text: string): Promise<{ recordset: Array<Record<string, unknown>> }> {
26
+ executedSQL.push(text);
27
+ return { recordset: [] };
28
+ }
29
+ },
30
+ },
31
+ }));
32
+
33
+ import { GetDataResolver, GetDataInputType, GetDataOutputType, RegisterAccessToken } from '../GetDataResolver.js';
34
+ import type { AppContext } from '../../types.js';
35
+
36
+ const CONTEXT = { dataSources: [] } as unknown as AppContext;
37
+
38
+ let tokenSeq = 0;
39
+
40
+ /** Calls GetData with a fresh valid token. */
41
+ async function runGetData(queries: string[]): Promise<GetDataOutputType> {
42
+ const token = `getdata-validation-${Date.now()}-${++tokenSeq}`;
43
+ RegisterAccessToken(token, 60_000);
44
+ const input = Object.assign(new GetDataInputType(), { Token: token, Queries: queries });
45
+ return new GetDataResolver().GetData(input, CONTEXT);
46
+ }
47
+
48
+ /** The text of every call to the validator's `validate` method. */
49
+ function validatedTexts(spy: MockInstance<SQLExpressionValidator['validate']>): string[] {
50
+ return spy.mock.calls.map(([text]) => text);
51
+ }
52
+
53
+ describe('GetData runs exactly the SQL it validated', () => {
54
+ beforeEach(() => {
55
+ executedSQL.length = 0;
56
+ });
57
+
58
+ it('validates and runs the same text when the query contains backslash sequences', async () => {
59
+ const query = String.raw`SELECT Name FROM __mj.vwFiles WHERE Path = 'C:\new\temp'`;
60
+ const validateSpy = vi.spyOn(SQLExpressionValidator.Instance, 'validate');
61
+
62
+ const output = await runGetData([query]);
63
+
64
+ expect(output.ErrorMessages).toEqual([null]);
65
+ expect(executedSQL).toEqual(validatedTexts(validateSpy));
66
+ expect(executedSQL).toEqual([query]);
67
+ expect(validateSpy).toHaveBeenCalledWith(query, { context: 'full_query', Dialect: 'sqlserver' });
68
+ });
69
+
70
+ it('runs the trimmed text that the validator reads', async () => {
71
+ const validateSpy = vi.spyOn(SQLExpressionValidator.Instance, 'validate');
72
+
73
+ const output = await runGetData(['\n SELECT 1 AS One \n']);
74
+
75
+ expect(output.ErrorMessages).toEqual([null]);
76
+ expect(executedSQL).toEqual(['SELECT 1 AS One']);
77
+ expect(validatedTexts(validateSpy)).toEqual(executedSQL);
78
+ });
79
+
80
+ it('refuses a query whose text, as written, does not open with SELECT or WITH', async () => {
81
+ const output = await runGetData(["-- note\\n SELECT 1\nPRINT 'hello'"]);
82
+
83
+ expect(executedSQL).toEqual([]);
84
+ expect(output.Success).toBe(false);
85
+ expect(output.ErrorMessages[0]).toMatch(/must start with SELECT or WITH/);
86
+ });
87
+
88
+ it('does not run a refused query and still runs the others', async () => {
89
+ const output = await runGetData(['SELECT 1; SELECT 2', 'SELECT 1 AS One']);
90
+
91
+ expect(executedSQL).toEqual(['SELECT 1 AS One']);
92
+ expect(output.ErrorMessages[0]).toMatch(/Semicolons are not allowed/);
93
+ expect(output.ErrorMessages[1]).toBeNull();
94
+ });
95
+ });
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @fileoverview Decides which callers of the agent-run resolvers may set a run's scope or agent-type parameters
3
+ * through the free-form `data` argument.
4
+ *
5
+ * `BaseAgent` and `AgentRunner` read a few `ExecuteAgentParams.data` keys (`RESERVED_AGENT_RUN_DATA_KEYS` in
6
+ * `@memberjunction/ai-core-plus`) as fallbacks that change what a run is, not just what its prompt says:
7
+ * - `PrimaryScopeEntityName`, `PrimaryScopeRecordID`, `SecondaryScopes` decide the run's scope: which
8
+ * notes and examples memory is injected, the pre-execution RAG search context, the scoped prompt parts
9
+ * and run settings, the scope columns written on the `MJ: AI Agent Runs` row, and what sub-agents
10
+ * inherit. On a widget-guest run an explicit scope also beats the returning-visitor scope.
11
+ * - `__agentTypePromptParams` is the highest-precedence agent-type parameter layer. It can turn on the
12
+ * Loop type's `enableTaskGraphs` capability, and switch decisions on or off.
13
+ *
14
+ * The rule lives with the run, not here: `BaseAgent.Execute` drops those keys from every run's `data` unless the
15
+ * params set `ExecuteAgentParams.TrustReservedRunData`. The resolvers set that marker for the callers
16
+ * {@link IsTrustedAgentRunCaller} accepts — the system user, and an `mj_sk_*` API key that holds the
17
+ * {@link TRUSTED_RUN_DATA_SCOPE} scope — and never for a browser or a widget guest. Any user who may create API
18
+ * keys can mint one for themselves, so a key alone is not enough: an administrator grants the scope. Explorer never
19
+ * sends these keys. Everything else in `data` (template data, `conversationId`, `appContext`, `clientTools`, ...)
20
+ * reaches the run unchanged either way.
21
+ *
22
+ * @module @memberjunction/server/resolvers
23
+ */
24
+ import { LogError } from '@memberjunction/core';
25
+ import { GetAPIKeyEngine } from '@memberjunction/api-keys';
26
+ import { UserCache } from '@memberjunction/generic-database-provider';
27
+ import type { UserPayload } from '../types.js';
28
+
29
+ /**
30
+ * The API-key scope (`MJ: API Scopes.FullPath`) that makes an `mj_sk_*` key a trusted integration for reserved run
31
+ * data. Checked against the agent being run, so a key can be trusted for some agents only. `full_access` does not
32
+ * imply it: the key must hold this scope itself.
33
+ */
34
+ export const TRUSTED_RUN_DATA_SCOPE = 'agent:trusted';
35
+
36
+ /** Who sent a run's `data`, as far as {@link IsTrustedAgentRunCaller} needs to know. */
37
+ export interface ClientAgentRunCaller {
38
+ /**
39
+ * The request's authenticated payload, BEFORE any widget-guest elevation. `isSystemUser` is set only by
40
+ * the system API key (and by widget elevation, which {@link IsWidgetGuestRun} overrides); `apiKeyHash`
41
+ * only by an `mj_sk_*` user API key.
42
+ */
43
+ UserPayload: Pick<UserPayload, 'email' | 'isSystemUser' | 'apiKeyHash'>;
44
+ /** True when the run executes under the widget-guest elevated principal. Such a run is never trusted. */
45
+ IsWidgetGuestRun: boolean;
46
+ /** The agent the caller asked to run: the resource the {@link TRUSTED_RUN_DATA_SCOPE} check is made against. */
47
+ AgentID: string;
48
+ }
49
+
50
+ /**
51
+ * Whether a caller may set the reserved run-data keys — the value the agent-run resolvers pass as
52
+ * `ExecuteAgentParams.TrustReservedRunData`: the system user, or an API key that holds
53
+ * {@link TRUSTED_RUN_DATA_SCOPE} for this agent, and never a widget-guest run, whatever principal it was elevated to.
54
+ * A key without the scope is treated like the browser session of the user it belongs to.
55
+ *
56
+ * @param caller - Who sent the run's `data`.
57
+ * @returns `true` when the run may read the reserved keys from `data`.
58
+ */
59
+ export async function IsTrustedAgentRunCaller(caller: ClientAgentRunCaller): Promise<boolean> {
60
+ if (caller.IsWidgetGuestRun) {
61
+ return false;
62
+ }
63
+ if (caller.UserPayload.isSystemUser === true) {
64
+ return true;
65
+ }
66
+ const apiKeyHash = caller.UserPayload.apiKeyHash;
67
+ return apiKeyHash ? keyHoldsTrustedRunDataScope(apiKeyHash, caller.AgentID) : false;
68
+ }
69
+
70
+ /**
71
+ * Whether the key explicitly holds {@link TRUSTED_RUN_DATA_SCOPE} for the agent. A key-level rule must match: an
72
+ * allow that comes from scope enforcement being switched off, or from `defaultBehaviorNoScopes: 'allow'`, matches
73
+ * no rule and does not count. Any failure counts as not held.
74
+ */
75
+ async function keyHoldsTrustedRunDataScope(apiKeyHash: string, agentID: string): Promise<boolean> {
76
+ try {
77
+ const systemUser = UserCache.Instance.GetSystemUser();
78
+ if (!systemUser) {
79
+ return false;
80
+ }
81
+ // A probe, not an access decision (the run goes ahead either way), so it is not written to the usage log.
82
+ const result = await GetAPIKeyEngine().Authorize(apiKeyHash, 'MJAPI', TRUSTED_RUN_DATA_SCOPE, agentID, systemUser,
83
+ { endpoint: '/graphql', method: 'POST' }, { skipLogging: true });
84
+ return result.Allowed && result.MatchedKeyRule != null;
85
+ } catch (e) {
86
+ LogError(`IsTrustedAgentRunCaller: the ${TRUSTED_RUN_DATA_SCOPE} check failed; the run is not trusted: ${e}`);
87
+ return false;
88
+ }
89
+ }