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

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 (560) hide show
  1. package/README.md +47 -1
  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 +17 -3
  8. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  9. package/dist/agentSessions/SessionManager.js +44 -10
  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 +27 -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 +52 -10
  25. package/dist/auth/index.js.map +1 -1
  26. package/dist/auth/initializeProviders.d.ts +10 -0
  27. package/dist/auth/initializeProviders.d.ts.map +1 -1
  28. package/dist/auth/initializeProviders.js +37 -1
  29. package/dist/auth/initializeProviders.js.map +1 -1
  30. package/dist/auth/localDevAuth.d.ts +79 -0
  31. package/dist/auth/localDevAuth.d.ts.map +1 -0
  32. package/dist/auth/localDevAuth.js +439 -0
  33. package/dist/auth/localDevAuth.js.map +1 -0
  34. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  35. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  36. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  37. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  39. package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
  40. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  41. package/dist/auth/magicLink/types.d.ts +12 -0
  42. package/dist/auth/magicLink/types.d.ts.map +1 -1
  43. package/dist/auth/sessionUserClone.d.ts +1 -1
  44. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  45. package/dist/auth/sessionUserClone.js +3 -2
  46. package/dist/auth/sessionUserClone.js.map +1 -1
  47. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  48. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  49. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  50. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  51. package/dist/broadcast/index.d.ts +6 -0
  52. package/dist/broadcast/index.d.ts.map +1 -0
  53. package/dist/broadcast/index.js +6 -0
  54. package/dist/broadcast/index.js.map +1 -0
  55. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  56. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  57. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  58. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  59. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  60. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  61. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  62. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  63. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  64. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  65. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  66. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  67. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  68. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  69. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  70. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  71. package/dist/cache.d.ts +3 -2
  72. package/dist/cache.d.ts.map +1 -1
  73. package/dist/cache.js.map +1 -1
  74. package/dist/config.d.ts +632 -22
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +104 -4
  77. package/dist/config.js.map +1 -1
  78. package/dist/context.d.ts +3 -1
  79. package/dist/context.d.ts.map +1 -1
  80. package/dist/context.js +29 -8
  81. package/dist/context.js.map +1 -1
  82. package/dist/coreRoutes.d.ts +29 -0
  83. package/dist/coreRoutes.d.ts.map +1 -0
  84. package/dist/coreRoutes.js +156 -0
  85. package/dist/coreRoutes.js.map +1 -0
  86. package/dist/generated/graphql-schemas/__mj.d.ts +1016 -29
  87. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  88. package/dist/generated/graphql-schemas/__mj.js +5125 -434
  89. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  90. package/dist/generic/CacheInvalidationResolver.d.ts +85 -30
  91. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  92. package/dist/generic/CacheInvalidationResolver.js +175 -60
  93. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  94. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  95. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  96. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  97. package/dist/generic/PubSubManager.d.ts +10 -0
  98. package/dist/generic/PubSubManager.d.ts.map +1 -1
  99. package/dist/generic/PubSubManager.js +44 -1
  100. package/dist/generic/PubSubManager.js.map +1 -1
  101. package/dist/generic/PushStatusResolver.d.ts +39 -6
  102. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  103. package/dist/generic/PushStatusResolver.js +94 -12
  104. package/dist/generic/PushStatusResolver.js.map +1 -1
  105. package/dist/generic/ResolverBase.d.ts +168 -69
  106. package/dist/generic/ResolverBase.d.ts.map +1 -1
  107. package/dist/generic/ResolverBase.js +464 -405
  108. package/dist/generic/ResolverBase.js.map +1 -1
  109. package/dist/generic/RunViewResolver.d.ts +11 -3
  110. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  111. package/dist/generic/RunViewResolver.js +91 -13
  112. package/dist/generic/RunViewResolver.js.map +1 -1
  113. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  114. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  115. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  116. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  117. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  118. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  119. package/dist/generic/fieldSecurityTransport.js +30 -0
  120. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  121. package/dist/globalCors.d.ts +33 -0
  122. package/dist/globalCors.d.ts.map +1 -0
  123. package/dist/globalCors.js +58 -0
  124. package/dist/globalCors.js.map +1 -0
  125. package/dist/index.d.ts +14 -0
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +230 -222
  128. package/dist/index.js.map +1 -1
  129. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  130. package/dist/integration/CustomColumnPromoter.js +6 -1
  131. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  132. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  133. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  134. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  135. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  136. package/dist/multiTenancy/index.d.ts.map +1 -1
  137. package/dist/multiTenancy/index.js +22 -2
  138. package/dist/multiTenancy/index.js.map +1 -1
  139. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -1
  140. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +2 -1
  141. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -1
  142. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -1
  143. package/dist/realtimeSessions/RealtimeVerifyRouter.js +2 -1
  144. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -1
  145. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
  146. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
  147. package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
  148. package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
  149. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  150. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  151. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  152. package/dist/realtimeSessions/index.d.ts +2 -0
  153. package/dist/realtimeSessions/index.d.ts.map +1 -1
  154. package/dist/realtimeSessions/index.js +2 -0
  155. package/dist/realtimeSessions/index.js.map +1 -1
  156. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +32 -0
  157. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
  158. package/dist/realtimeSessions/ownedRealtimeSession.js +56 -0
  159. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
  160. package/dist/realtimeSessions/sessionAccess.d.ts +91 -8
  161. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  162. package/dist/realtimeSessions/sessionAccess.js +127 -20
  163. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  164. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  165. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  166. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  167. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  168. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  169. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  170. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  171. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  172. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  173. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  174. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  175. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  176. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  177. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  178. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  179. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  180. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  181. package/dist/realtimeWidget/host-identity.js +1 -1
  182. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  183. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  184. package/dist/realtimeWidget/widgetCore.js +17 -1
  185. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  186. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  187. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  188. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  189. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  190. package/dist/resolvers/ActionResolver.d.ts +19 -1
  191. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  192. package/dist/resolvers/ActionResolver.js +63 -12
  193. package/dist/resolvers/ActionResolver.js.map +1 -1
  194. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  195. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  196. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  197. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  198. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  199. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  200. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  201. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  202. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  203. package/dist/resolvers/AgentSessionResolver.js +40 -17
  204. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  205. package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
  206. package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
  207. package/dist/resolvers/ArtifactFileResolver.js +9 -7
  208. package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
  209. package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
  210. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  211. package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
  212. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  213. package/dist/resolvers/DatasetResolver.d.ts +24 -3
  214. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  215. package/dist/resolvers/DatasetResolver.js +57 -8
  216. package/dist/resolvers/DatasetResolver.js.map +1 -1
  217. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  218. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  219. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  220. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  221. package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
  222. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  223. package/dist/resolvers/EntityRecordNameResolver.js +3 -2
  224. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  225. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +46 -3
  226. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  227. package/dist/resolvers/FetchEntityVectorsResolver.js +211 -50
  228. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  229. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  230. package/dist/resolvers/FileCategoryResolver.js +1 -0
  231. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  232. package/dist/resolvers/FileResolver.d.ts +87 -11
  233. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  234. package/dist/resolvers/FileResolver.js +292 -105
  235. package/dist/resolvers/FileResolver.js.map +1 -1
  236. package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
  237. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  238. package/dist/resolvers/GetDataContextDataResolver.js +117 -38
  239. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  240. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  241. package/dist/resolvers/GetDataResolver.js +6 -3
  242. package/dist/resolvers/GetDataResolver.js.map +1 -1
  243. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  244. package/dist/resolvers/IntegrationDiscoveryResolver.js +49 -9
  245. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  246. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  247. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  248. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  249. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  250. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  251. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  252. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  253. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  254. package/dist/resolvers/QueryResolver.d.ts +2 -0
  255. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  256. package/dist/resolvers/QueryResolver.js +33 -6
  257. package/dist/resolvers/QueryResolver.js.map +1 -1
  258. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  259. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  260. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  261. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  262. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  263. package/dist/resolvers/RSUResolver.js +12 -0
  264. package/dist/resolvers/RSUResolver.js.map +1 -1
  265. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +18 -10
  266. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  267. package/dist/resolvers/RealtimeClientSessionResolver.js +92 -54
  268. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  269. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  270. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  271. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  272. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  273. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  274. package/dist/resolvers/RunAIAgentResolver.js +241 -67
  275. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  276. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  277. package/dist/resolvers/RunTemplateResolver.js +4 -4
  278. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  279. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  280. package/dist/resolvers/RunTestResolver.js +3 -1
  281. package/dist/resolvers/RunTestResolver.js.map +1 -1
  282. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
  283. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
  284. package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
  285. package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
  286. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  287. package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
  288. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  289. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  290. package/dist/resolvers/SyncDataResolver.js +4 -0
  291. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  292. package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
  293. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  294. package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
  295. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  296. package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
  297. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  298. package/dist/resolvers/TestQuerySQLResolver.js +66 -13
  299. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  300. package/dist/resolvers/TransactionGroupResolver.d.ts +34 -2
  301. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  302. package/dist/resolvers/TransactionGroupResolver.js +150 -27
  303. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  304. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  305. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  306. package/dist/resolvers/UserViewResolver.js +2 -1
  307. package/dist/resolvers/UserViewResolver.js.map +1 -1
  308. package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
  309. package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
  310. package/dist/resolvers/agent-run-data-guard.js +72 -0
  311. package/dist/resolvers/agent-run-data-guard.js.map +1 -0
  312. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  313. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  314. package/dist/rest/EntityCRUDHandler.js +19 -5
  315. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  316. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  317. package/dist/rest/RESTEndpointHandler.js +8 -5
  318. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  319. package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
  320. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
  321. package/dist/rest/RealtimeRecordingHandler.js +383 -0
  322. package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
  323. package/dist/rest/ViewOperationsHandler.d.ts +9 -1
  324. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  325. package/dist/rest/ViewOperationsHandler.js +43 -17
  326. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  327. package/dist/rest/setupRESTEndpoints.d.ts +6 -0
  328. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  329. package/dist/rest/setupRESTEndpoints.js +6 -0
  330. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  331. package/dist/serverExtensionReservedRoots.d.ts +12 -10
  332. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  333. package/dist/serverExtensionReservedRoots.js +14 -17
  334. package/dist/serverExtensionReservedRoots.js.map +1 -1
  335. package/dist/serverRoleConfigUnits.d.ts +29 -0
  336. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  337. package/dist/serverRoleConfigUnits.js +33 -0
  338. package/dist/serverRoleConfigUnits.js.map +1 -0
  339. package/dist/services/MJStorageBlobStore.d.ts +8 -1
  340. package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
  341. package/dist/services/MJStorageBlobStore.js +25 -19
  342. package/dist/services/MJStorageBlobStore.js.map +1 -1
  343. package/dist/services/StartTaskGraphDispatcher.js +2 -2
  344. package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
  345. package/dist/services/TaskGraphActionRunner.d.ts +5 -3
  346. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  347. package/dist/services/TaskGraphActionRunner.js +27 -5
  348. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  349. package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
  350. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  351. package/dist/services/TaskGraphAgentRunner.js +157 -9
  352. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  353. package/dist/services/TaskGraphContinuationDeliverer.d.ts +59 -2
  354. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  355. package/dist/services/TaskGraphContinuationDeliverer.js +626 -16
  356. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  357. package/dist/sharedCache.d.ts.map +1 -1
  358. package/dist/sharedCache.js +3 -0
  359. package/dist/sharedCache.js.map +1 -1
  360. package/dist/types.d.ts +23 -1
  361. package/dist/types.d.ts.map +1 -1
  362. package/dist/types.js.map +1 -1
  363. package/package.json +99 -98
  364. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  365. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  366. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  367. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  368. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  369. package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
  370. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  371. package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
  372. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  373. package/src/__tests__/FetchEntityVectorsResolver.test.ts +369 -18
  374. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  375. package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
  376. package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
  377. package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
  378. package/src/__tests__/LoginScreenEngine.test.ts +86 -0
  379. package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
  380. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  381. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  382. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  383. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  384. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  385. package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
  386. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  387. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
  388. package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
  389. package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
  390. package/src/__tests__/ResolverBase.clientOldValues.test.ts +243 -7
  391. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  392. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  393. package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
  394. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  395. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  396. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  397. package/src/__tests__/RunAIAgentResolver.branch.test.ts +69 -13
  398. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  399. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  400. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +20 -28
  401. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +264 -0
  402. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  403. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  404. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  405. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  406. package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
  407. package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
  408. package/src/__tests__/SessionJanitor.test.ts +365 -10
  409. package/src/__tests__/SessionManager.test.ts +122 -0
  410. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
  411. package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
  412. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +468 -23
  413. package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
  414. package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
  415. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  416. package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
  417. package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
  418. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  419. package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
  420. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  421. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  422. package/src/__tests__/agent-run-data-guard.test.ts +130 -0
  423. package/src/__tests__/apiRootBoot.test.ts +202 -0
  424. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  425. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  426. package/src/__tests__/cacheInvalidationRecordData.test.ts +109 -0
  427. package/src/__tests__/conversationRunEvents.test.ts +56 -14
  428. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  429. package/src/__tests__/globalCors.test.ts +217 -0
  430. package/src/__tests__/localDevAuth.test.ts +196 -0
  431. package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
  432. package/src/__tests__/multiTenancy.security.test.ts +51 -0
  433. package/src/__tests__/multiTenancy.test.ts +1 -1
  434. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  435. package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
  436. package/src/__tests__/pushStatusResolver.filter.test.ts +64 -1
  437. package/src/__tests__/realtimeRecordingHandler.test.ts +669 -0
  438. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  439. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  440. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  441. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  442. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  443. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  444. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  445. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  446. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  447. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  448. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  449. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  450. package/src/__tests__/runViewProperties.test.ts +114 -0
  451. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  452. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  453. package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
  454. package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
  455. package/src/__tests__/sessionUserClone.test.ts +5 -1
  456. package/src/__tests__/sharedCache.test.ts +5 -3
  457. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  458. package/src/__tests__/unifiedAuth.test.ts +28 -0
  459. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  460. package/src/agentSessions/SessionJanitor.ts +369 -93
  461. package/src/agentSessions/SessionManager.ts +45 -10
  462. package/src/apiRoot.ts +78 -0
  463. package/src/auth/AuthProviderCatalogRouter.ts +29 -0
  464. package/src/auth/LoginScreenEngine.ts +50 -0
  465. package/src/auth/index.ts +57 -12
  466. package/src/auth/initializeProviders.ts +43 -1
  467. package/src/auth/localDevAuth.ts +528 -0
  468. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  469. package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
  470. package/src/auth/magicLink/types.ts +10 -0
  471. package/src/auth/sessionUserClone.ts +3 -2
  472. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  473. package/src/broadcast/index.ts +5 -0
  474. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  475. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  476. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  477. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  478. package/src/cache.ts +2 -1
  479. package/src/config.ts +115 -5
  480. package/src/context.ts +32 -8
  481. package/src/coreRoutes.ts +181 -0
  482. package/src/generated/graphql-schemas/__mj.ts +3684 -284
  483. package/src/generic/CacheInvalidationResolver.ts +172 -60
  484. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  485. package/src/generic/PubSubManager.ts +54 -1
  486. package/src/generic/PushStatusResolver.ts +109 -12
  487. package/src/generic/ResolverBase.ts +540 -431
  488. package/src/generic/RunViewResolver.ts +97 -15
  489. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  490. package/src/generic/fieldSecurityTransport.ts +62 -0
  491. package/src/globalCors.ts +71 -0
  492. package/src/index.ts +239 -238
  493. package/src/integration/CustomColumnPromoter.ts +6 -1
  494. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  495. package/src/multiTenancy/index.ts +24 -2
  496. package/src/realtimeSessions/README.md +7 -3
  497. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
  498. package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
  499. package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
  500. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  501. package/src/realtimeSessions/index.ts +2 -0
  502. package/src/realtimeSessions/ownedRealtimeSession.ts +69 -0
  503. package/src/realtimeSessions/sessionAccess.ts +183 -23
  504. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  505. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  506. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  507. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  508. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  509. package/src/realtimeWidget/host-identity.ts +1 -1
  510. package/src/realtimeWidget/widgetCore.ts +30 -1
  511. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  512. package/src/resolvers/ActionResolver.ts +78 -15
  513. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  514. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  515. package/src/resolvers/AgentSessionResolver.ts +46 -18
  516. package/src/resolvers/ArtifactFileResolver.ts +9 -8
  517. package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
  518. package/src/resolvers/DatasetResolver.ts +70 -10
  519. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  520. package/src/resolvers/EntityRecordNameResolver.ts +3 -2
  521. package/src/resolvers/FetchEntityVectorsResolver.ts +248 -55
  522. package/src/resolvers/FileCategoryResolver.ts +1 -0
  523. package/src/resolvers/FileResolver.ts +334 -109
  524. package/src/resolvers/GetDataContextDataResolver.ts +139 -41
  525. package/src/resolvers/GetDataResolver.ts +6 -3
  526. package/src/resolvers/IntegrationDiscoveryResolver.ts +53 -9
  527. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  528. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  529. package/src/resolvers/QueryResolver.ts +31 -6
  530. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  531. package/src/resolvers/RSUResolver.ts +12 -0
  532. package/src/resolvers/RealtimeClientSessionResolver.ts +114 -66
  533. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  534. package/src/resolvers/RunAIAgentResolver.ts +296 -85
  535. package/src/resolvers/RunTemplateResolver.ts +6 -4
  536. package/src/resolvers/RunTestResolver.ts +3 -1
  537. package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
  538. package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
  539. package/src/resolvers/SyncDataResolver.ts +4 -0
  540. package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
  541. package/src/resolvers/TestQuerySQLResolver.ts +69 -13
  542. package/src/resolvers/TransactionGroupResolver.ts +185 -30
  543. package/src/resolvers/UserViewResolver.ts +2 -1
  544. package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
  545. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  546. package/src/resolvers/agent-run-data-guard.ts +89 -0
  547. package/src/rest/EntityCRUDHandler.ts +22 -5
  548. package/src/rest/RESTEndpointHandler.ts +8 -5
  549. package/src/rest/RealtimeRecordingHandler.ts +467 -0
  550. package/src/rest/ViewOperationsHandler.ts +52 -18
  551. package/src/rest/setupRESTEndpoints.ts +6 -0
  552. package/src/serverExtensionReservedRoots.ts +14 -17
  553. package/src/serverRoleConfigUnits.ts +48 -0
  554. package/src/services/MJStorageBlobStore.ts +31 -30
  555. package/src/services/StartTaskGraphDispatcher.ts +2 -2
  556. package/src/services/TaskGraphActionRunner.ts +25 -5
  557. package/src/services/TaskGraphAgentRunner.ts +188 -11
  558. package/src/services/TaskGraphContinuationDeliverer.ts +738 -16
  559. package/src/sharedCache.ts +3 -0
  560. package/src/types.ts +23 -1
@@ -1,7 +1,6 @@
1
- import { EntityPermissionType, FieldValueCollection, EntitySaveOptions, LogError, UserInfo } from '@memberjunction/core';
1
+ import { AssertClientInputWritableForUser, EntityPermissionType, FieldValueCollection, EntitySaveOptions, IMetadataProvider, LogError, UserInfo } from '@memberjunction/core';
2
2
  import { NormalizeUUID } from '@memberjunction/global';
3
3
  import { MJFileEntity, MJFileStorageProviderEntity, MJFileStorageAccountEntity } from '@memberjunction/core-entities';
4
- import { readRealtimeRecordingFile } from '@memberjunction/ai-agents';
5
4
  import {
6
5
  AppContext,
7
6
  Arg,
@@ -35,14 +34,24 @@ import {
35
34
  UserContextOptions,
36
35
  ExtendedUserContextOptions,
37
36
  FileStorageEngine,
37
+ FileStorageBase,
38
+ MultiAccountSearchResult,
39
+ ResolvedFileObject,
40
+ StorageAccessEvaluator,
41
+ StorageAccountAccess,
42
+ StorageAccountAccessDeniedError,
43
+ StorageFileLocation,
44
+ StorageObjectKeyOf,
45
+ StorageObjectMetadata as StoredObjectMetadata,
38
46
  } from '@memberjunction/storage';
39
47
  import { CreateMJFileInput, MJFileResolver as FileResolverBase, MJFile_, UpdateMJFileInput } from '../generated/generated.js';
40
48
  import { FieldMapper } from '@memberjunction/graphql-dataprovider';
41
49
  import { GetReadOnlyProvider } from '../util.js';
42
- import { configInfo } from '../config.js';
50
+ import { ProjectEntityObjectForCaller, ProjectableEntityObject } from '../generic/fieldSecurityTransport.js';
43
51
  import { MediaAccessKeyManager } from '../rest/MediaAccessKeys.js';
44
52
  import { UploadTokenManager } from '../rest/UploadTokenManager.js';
45
53
  import { DeriveSidecarPath, ParsePeaksSidecar } from './peaksSidecar.js';
54
+ import { ApiRoute } from '../apiRoot.js';
46
55
 
47
56
  @InputType()
48
57
  export class CreateUploadURLInput {
@@ -434,18 +443,11 @@ export class MediaAccessTokenResult {
434
443
  ErrorMessage?: string;
435
444
  }
436
445
 
446
+ /** The failure reported for an account `SearchAcrossAccounts` refuses — the same whether it is restricted or does not exist. */
447
+ const SEARCH_ACCOUNT_REFUSED_MESSAGE = 'Access denied: you do not have access to this storage account or it does not exist.';
448
+
437
449
  @Resolver(MJFile_)
438
450
  export class FileResolver extends FileResolverBase {
439
- /**
440
- * Resolves the server's public base URL the same way callbacks/magic-link do:
441
- * `publicUrl` if configured (e.g. an ngrok URL), else `baseUrl:graphqlPort`. Trailing
442
- * slash stripped so callers can append `/media/...`.
443
- */
444
- private resolvePublicBaseUrl(): string {
445
- const base = configInfo.publicUrl || `${configInfo.baseUrl}:${configInfo.graphqlPort}${configInfo.graphqlRootPath || ''}`;
446
- return base.replace(/\/+$/, '');
447
- }
448
-
449
451
  /**
450
452
  * Mints a short-lived signed media-access token for an `MJ: Files` record and returns the
451
453
  * authenticated streaming URL (`<publicBase>/media/<fileId>?token=<token>`). Permission-gated
@@ -469,16 +471,19 @@ export class FileResolver extends FileResolverBase {
469
471
  if (!loaded) {
470
472
  return { Success: false, ErrorMessage: 'You do not have access to this file or it does not exist.' };
471
473
  }
474
+ // Storage gate: the token is the capability the /media route honours, so it is minted only when the caller may read
475
+ // the account the row's provider resolves to and every row tracking the object (throws — caught below).
476
+ const resolved = await this.assertFileObjectAccess(fileEntity, 'Read', contextUser, provider);
472
477
 
473
478
  // Access authorized — mint the capability token.
474
479
  const { Token, ExpiresAt } = MediaAccessKeyManager.Instance.Sign(fileId, contextUser.ID);
475
480
  const fileNameSuffix = fileEntity.Name ? `/${encodeURIComponent(fileEntity.Name)}` : '';
476
- const url = `${this.resolvePublicBaseUrl()}/media/${encodeURIComponent(fileId)}${fileNameSuffix}?token=${encodeURIComponent(Token)}`;
481
+ const url = ApiRoute(`/media/${encodeURIComponent(fileId)}${fileNameSuffix}?token=${encodeURIComponent(Token)}`, { absolute: true });
477
482
 
478
483
  // Best-effort: surface precomputed waveform peaks from a peaks.json sidecar beside the file, so
479
484
  // the player renders the real waveform instantly without fetching/decoding the audio. Never
480
485
  // blocks token minting — any failure just omits Peaks.
481
- const peaks = await this.tryReadPeaksSidecar(fileEntity, contextUser);
486
+ const peaks = await this.tryReadPeaksSidecar(fileEntity, resolved?.Driver);
482
487
 
483
488
  return { Success: true, Token, Url: url, ExpiresAt, MimeType: fileEntity.ContentType ?? undefined, Peaks: peaks };
484
489
  } catch (error) {
@@ -492,34 +497,22 @@ export class FileResolver extends FileResolverBase {
492
497
  * Best-effort read of a `peaks.json` waveform sidecar that sits in the SAME storage folder as the
493
498
  * given file (a JSON array of normalized `0..1` numbers, written at capture time). Derives the
494
499
  * folder from the file's `ProviderKey` (strips the final path segment), reads `<folder>/peaks.json`
495
- * via the file's own storage driver, parses + validates it, and returns sanitized peaks. Returns
496
- * `undefined` on ANY failure (no ProviderKey, no sidecar, parse error, garbage) — the caller treats
497
- * peaks as a pure optimization and must never let a sidecar problem affect token minting.
500
+ * through the file's own storage driver — the one {@link assertFileObjectAccess} resolved, so the account is already
501
+ * gated — parses + validates it, and returns sanitized peaks. Returns `undefined` on ANY failure (no ProviderKey, no
502
+ * driver, no sidecar, parse error, garbage) — the caller treats peaks as a pure optimization and must never let a
503
+ * sidecar problem affect token minting.
498
504
  *
499
505
  * @param file The already-loaded (under the user's context) `MJ: Files` row.
500
- * @param contextUser The calling user — used to resolve the storage driver.
506
+ * @param driver The gated driver for the file's account, or undefined when the provider has none.
501
507
  * @returns Sanitized `0..1` peaks (length-capped), or `undefined`.
502
508
  */
503
- private async tryReadPeaksSidecar(file: MJFileEntity, contextUser: UserInfo): Promise<number[] | undefined> {
509
+ private async tryReadPeaksSidecar(file: MJFileEntity, driver: FileStorageBase | undefined): Promise<number[] | undefined> {
504
510
  try {
505
511
  // The sidecar lives next to the recording: replace the final path segment with peaks.json.
506
512
  const sidecarPath = DeriveSidecarPath(file.ProviderKey);
507
- if (!sidecarPath) {
508
- return undefined;
509
- }
510
-
511
- // Resolve the file's storage account → driver (mirror of readRealtimeRecordingFile's pattern).
512
- await FileStorageEngine.Instance.Config(false, contextUser);
513
- let accounts = FileStorageEngine.Instance.GetAccountsByProviderID(file.ProviderID);
514
- if (accounts.length === 0) {
515
- await FileStorageEngine.Instance.Config(true, contextUser);
516
- accounts = FileStorageEngine.Instance.GetAccountsByProviderID(file.ProviderID);
517
- }
518
- const account = accounts[0];
519
- if (!account) {
513
+ if (!sidecarPath || !driver) {
520
514
  return undefined;
521
515
  }
522
- const driver = await FileStorageEngine.Instance.GetDriver(account.ID, contextUser);
523
516
  const bytes = await driver.GetObject({ fullPath: sidecarPath });
524
517
  if (!bytes || bytes.length === 0) {
525
518
  return undefined;
@@ -557,6 +550,75 @@ export class FileResolver extends FileResolverBase {
557
550
  };
558
551
  }
559
552
 
553
+ /**
554
+ * The storage-account gate. Every route keyed by a client-supplied account ID runs it FIRST — before the account is
555
+ * loaded and before any driver call. Throws `StorageAccountAccessDeniedError` ("You do not have access to this storage
556
+ * account or it does not exist.") unless the caller may use the account for `access`, evaluated per call against
557
+ * `MJ: File Storage Account Permissions` by `StorageAccessEvaluator` (`@memberjunction/storage`). An unknown account
558
+ * gets the same refusal, so the error never reveals whether an account ID is real.
559
+ */
560
+ private async assertAccountAccess(accountId: string, access: StorageAccountAccess, context: AppContext): Promise<void> {
561
+ const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
562
+ const user = this.GetUserFromPayload(context.userPayload);
563
+ await StorageAccessEvaluator.Instance.AssertAccountAccess(accountId, user, access, md);
564
+ }
565
+
566
+ /**
567
+ * The tracked-file rule for an account-keyed route: when any of `objectNames` in the account backs an `MJ: Files` row,
568
+ * the caller must be able to read that row — the rule {@link GetFileContents} applies to a file ID — else this throws
569
+ * `TrackedFileAccessDeniedError` ("You do not have access to this file or it does not exist."). Keys are compared the
570
+ * way the account's driver addresses them (`FileStorageBase.NormalizeObjectKey`) and case-insensitively; a key whose
571
+ * meaning depends on the provider (`..`, `\`, `%2F`, ...) is refused outright. Applied to every object a route reads
572
+ * or signs, and to every object it overwrites, moves or deletes — the minimum for a write is that the caller may read
573
+ * what it replaces. An object no row tracks is governed by the account gate, which the caller has already passed.
574
+ */
575
+ private async assertTrackedObjectReadable(accountId: string, objectNames: string[], user: UserInfo, md: IMetadataProvider): Promise<void> {
576
+ const providerID = await this.accountProviderID(accountId, user, md);
577
+ const driver = await FileStorageEngine.Instance.GetDriver(accountId, user);
578
+ await StorageAccessEvaluator.Instance.AssertTrackedObjectsReadable(providerID, objectNames, user, md, driver);
579
+ }
580
+
581
+ /**
582
+ * The storage gate for a route that goes from an `MJ: Files` row (already loaded as the caller) to its object: the
583
+ * account the row's provider resolves to must allow `access`, and every row tracking the object must be readable by
584
+ * the caller (`FileStorageEngine.ResolveFileObject`) — so a row whose client-written `ProviderID`/`ProviderKey`/`Name`
585
+ * aliases someone else's object is refused. `extraKeys` (a rename target) get the tracked-file rule too. When the
586
+ * provider has no storage account the legacy env-configured driver serves the row; there is no account to gate, so the
587
+ * tracked-file rule alone applies. Returns the resolved object, or `null` in that legacy case.
588
+ */
589
+ private async assertFileObjectAccess(
590
+ file: StorageFileLocation,
591
+ access: StorageAccountAccess,
592
+ user: UserInfo,
593
+ md: IMetadataProvider,
594
+ extraKeys: string[] = [],
595
+ ): Promise<ResolvedFileObject | null> {
596
+ const location: StorageFileLocation = { ProviderID: file.ProviderID, ProviderKey: file.ProviderKey ?? null, Name: file.Name };
597
+ const resolved = await FileStorageEngine.Instance.ResolveFileObject(location, user, access, md);
598
+ const keys = resolved ? extraKeys : [StorageObjectKeyOf(location), ...extraKeys];
599
+ if (keys.length > 0) {
600
+ await StorageAccessEvaluator.Instance.AssertTrackedObjectsReadable(file.ProviderID, keys, user, md, resolved?.Driver);
601
+ }
602
+ return resolved;
603
+ }
604
+
605
+ /**
606
+ * The storage provider ID of an account the caller has already passed the gate for, from the engine's cache (refreshed
607
+ * once on a miss). Refuses like the gate when the account still cannot be found.
608
+ */
609
+ private async accountProviderID(accountId: string, user: UserInfo, md: IMetadataProvider): Promise<string> {
610
+ await FileStorageEngine.Instance.Config(false, user, md);
611
+ let account = FileStorageEngine.Instance.GetAccountById(accountId);
612
+ if (!account) {
613
+ await FileStorageEngine.Instance.RefreshAfterMiss(md);
614
+ account = FileStorageEngine.Instance.GetAccountById(accountId);
615
+ }
616
+ if (!account) {
617
+ throw new StorageAccountAccessDeniedError();
618
+ }
619
+ return account.ProviderID;
620
+ }
621
+
560
622
  /**
561
623
  * Loads a FileStorageAccount and its associated FileStorageProvider.
562
624
  * This is the standard way to get provider information in the enterprise model.
@@ -605,6 +667,13 @@ export class FileResolver extends FileResolverBase {
605
667
  const fileEntity = await provider.GetEntityObject<MJFileEntity>('MJ: Files', user);
606
668
  const providerEntity = await provider.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
607
669
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
670
+ // Field-level security: refuse a key the caller may not read, or that names no field, before anything else
671
+ AssertClientInputWritableForUser(fileEntity.EntityInfo, { ...input }, user);
672
+
673
+ // The upload URL signs an overwrite of `Name` (and the row will point at `ProviderKey`): Write on the account the
674
+ // provider resolves to, and the tracked-file rule on both keys, as for every other route that writes an object.
675
+ const location: StorageFileLocation = { ProviderID: input.ProviderID ?? '', ProviderKey: null, Name: input.Name ?? '' };
676
+ await this.assertFileObjectAccess(location, 'Write', user, provider, input.ProviderKey ? [input.ProviderKey] : []);
608
677
 
609
678
  const [sameName] = await this.findBy(provider, 'MJ: Files', { Name: input.Name, ProviderID: input.ProviderID }, context.userPayload.userRecord);
610
679
  const NameExists = Boolean(sameName);
@@ -627,7 +696,7 @@ export class FileResolver extends FileResolverBase {
627
696
  if (!saved) {
628
697
  console.error('[CreateFile] File save failed:', fileEntity.LatestResult?.CompleteMessage);
629
698
  }
630
- const File = mapper.MapFields({ ...fileEntity.GetAll() });
699
+ const File = this.fileResponse(fileEntity, user, provider);
631
700
 
632
701
  return { File, UploadUrl, NameExists };
633
702
  }
@@ -650,6 +719,13 @@ export class FileResolver extends FileResolverBase {
650
719
  const fileEntity = await provider.GetEntityObject<MJFileEntity>('MJ: Files', user);
651
720
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
652
721
 
722
+ // Write gate on the account the upload lands in — before the staged upload is consumed or any driver is touched.
723
+ // With no account at all there is nothing to gate; UploadFile then refuses with its documented error.
724
+ const targetAccountId = await this.resolveUploadAccountId(input.AccountID, user, provider);
725
+ if (targetAccountId) {
726
+ await this.assertAccountAccess(targetAccountId, 'Write', context);
727
+ }
728
+
653
729
  let buffer: Buffer;
654
730
  let fileName: string = input.FileName || 'file';
655
731
  let mimeType: string = input.MimeType || 'application/octet-stream';
@@ -674,18 +750,12 @@ export class FileResolver extends FileResolverBase {
674
750
  };
675
751
  }
676
752
 
677
- // Ensure FileStorageEngine is configured under the user context
678
- await FileStorageEngine.Instance.Config(false, user, provider);
679
- if (input.AccountID && !FileStorageEngine.Instance.GetAccountById(input.AccountID)) {
680
- await FileStorageEngine.Instance.Config(true, user, provider);
681
- }
682
-
683
753
  const uploadResult = await FileStorageEngine.Instance.UploadFile({
684
754
  content: buffer,
685
755
  fileName,
686
756
  mimeType,
687
757
  contextUser: user,
688
- storageAccountId: input.AccountID,
758
+ storageAccountId: targetAccountId,
689
759
  categoryID: input.CategoryID,
690
760
  description: input.Description,
691
761
  pathPrefix: input.PathPrefix,
@@ -693,8 +763,7 @@ export class FileResolver extends FileResolverBase {
693
763
  });
694
764
 
695
765
  const loaded = await fileEntity.Load(uploadResult.FileID);
696
- const mapper = new FieldMapper();
697
- const File = loaded ? (mapper.MapFields({ ...fileEntity.GetAll() }) as unknown as MJFile_) : undefined;
766
+ const File = loaded ? (this.fileResponse(fileEntity, user, provider) as unknown as MJFile_) : undefined;
698
767
 
699
768
  return {
700
769
  Success: true,
@@ -711,6 +780,28 @@ export class FileResolver extends FileResolverBase {
711
780
  }
712
781
  }
713
782
 
783
+ /**
784
+ * The file record for a response: its field values as the caller may read them, in GraphQL field
785
+ * names. The record was just saved or reloaded, and a save re-reads the whole row.
786
+ */
787
+ private fileResponse(fileEntity: ProjectableEntityObject, user: UserInfo, provider: IMetadataProvider): Record<string, unknown> {
788
+ return new FieldMapper().MapFields({ ...ProjectEntityObjectForCaller(fileEntity, user, provider) });
789
+ }
790
+
791
+ /**
792
+ * The account an upload will land in: the requested one, else the engine's default (the first active account) —
793
+ * returned as an explicit ID so the account the gate checks is the account `UploadFile` uses. A requested ID is
794
+ * returned as-is: the gate refuses an unknown one without revealing that it does not exist, and `UploadFile` refreshes
795
+ * the engine's cache itself when a permitted account is missing from it.
796
+ */
797
+ private async resolveUploadAccountId(accountId: string | undefined, user: UserInfo, md: IMetadataProvider): Promise<string | undefined> {
798
+ if (accountId) {
799
+ return accountId;
800
+ }
801
+ await FileStorageEngine.Instance.Config(false, user, md);
802
+ return FileStorageEngine.Instance.ResolveStorageAccount()?.account.ID;
803
+ }
804
+
714
805
  @FieldResolver(() => String)
715
806
  async DownloadUrl(@Root() file: MJFile_, @Ctx() context: AppContext) {
716
807
  const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
@@ -718,24 +809,18 @@ export class FileResolver extends FileResolverBase {
718
809
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
719
810
  fileEntity.CheckPermissions(EntityPermissionType.Read, true);
720
811
 
721
- await FileStorageEngine.Instance.Config(false, user, md);
722
- let accounts = FileStorageEngine.Instance.GetAccountsByProviderID(file.ProviderID);
723
- if (accounts.length === 0) {
724
- await FileStorageEngine.Instance.Config(true, user, md);
725
- accounts = FileStorageEngine.Instance.GetAccountsByProviderID(file.ProviderID);
726
- }
727
-
728
- if (accounts.length > 0) {
729
- const account = accounts[0];
730
- const driver = await FileStorageEngine.Instance.GetDriver(account.ID, user);
731
- return await driver.CreatePreAuthDownloadUrl(file.ProviderKey ?? file.Name);
812
+ // Storage gate: Read on the account the row's provider resolves to, and the tracked-file rule on its object
813
+ const location: StorageFileLocation = { ProviderID: file.ProviderID, ProviderKey: file.ProviderKey ?? null, Name: file.Name ?? '' };
814
+ const resolved = await this.assertFileObjectAccess(location, 'Read', user, md);
815
+ if (resolved) {
816
+ return await resolved.Driver.CreatePreAuthDownloadUrl(resolved.ObjectKey);
732
817
  }
733
818
 
734
819
  const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
735
820
  await providerEntity.Load(file.ProviderID);
736
821
 
737
822
  const userContext = this.buildUserContext(context);
738
- const url = await createDownloadUrl(providerEntity, file.ProviderKey ?? file.Name, userContext);
823
+ const url = await createDownloadUrl(providerEntity, StorageObjectKeyOf(file), userContext);
739
824
 
740
825
  return url;
741
826
  }
@@ -743,7 +828,9 @@ export class FileResolver extends FileResolverBase {
743
828
  /**
744
829
  * Returns an `MJ: Files` record's bytes as base64, read server-side through authenticated MJStorage
745
830
  * (`GetObject`) — NOT a public pre-signed link. Permission-gated: the file is first loaded under the
746
- * calling user's context, so MJ row-level security determines access. Never throws to the client.
831
+ * calling user's context, so MJ row-level security determines access; then the storage gate
832
+ * ({@link assertFileObjectAccess}) requires Read on the account the row's provider resolves to and refuses an object
833
+ * another row tracks when the caller cannot read that row. Never throws to the client.
747
834
  *
748
835
  * @param fileId The `MJ: Files` id whose bytes to return.
749
836
  * @returns `{ Success, Base64?, MimeType?, ErrorMessage? }`.
@@ -762,16 +849,18 @@ export class FileResolver extends FileResolverBase {
762
849
  return { Success: false, ErrorMessage: 'You do not have access to this file or it does not exist.' };
763
850
  }
764
851
 
765
- // Read the bytes via authenticated MJStorage (server-side GetObject on the file's own account).
766
- const result = await readRealtimeRecordingFile(fileId, contextUser, provider);
767
- if (!result) {
852
+ // Storage gate, then read the bytes via authenticated MJStorage (server-side GetObject on the file's own account).
853
+ // A refusal throws and is reported below; a row whose provider has no account has no bytes to read.
854
+ const resolved = await this.assertFileObjectAccess(fileEntity, 'Read', contextUser, provider);
855
+ const bytes = resolved ? await resolved.Driver.GetObject({ fullPath: resolved.ObjectKey }) : null;
856
+ if (!bytes || bytes.length === 0) {
768
857
  return { Success: false, ErrorMessage: 'The file could not be read from storage.' };
769
858
  }
770
859
 
771
860
  return {
772
861
  Success: true,
773
- Base64: result.Bytes.toString('base64'),
774
- MimeType: result.MimeType ?? fileEntity.ContentType ?? undefined,
862
+ Base64: bytes.toString('base64'),
863
+ MimeType: fileEntity.ContentType ?? undefined,
775
864
  };
776
865
  } catch (error) {
777
866
  const message = error instanceof Error ? error.message : String(error);
@@ -791,6 +880,8 @@ export class FileResolver extends FileResolverBase {
791
880
  await fileEntity.Load(input.ID);
792
881
 
793
882
  if (fileEntity.Name !== input.Name) {
883
+ // Renaming moves the object: Write on its account, and the tracked-file rule on the object and the new name
884
+ await this.assertFileObjectAccess(fileEntity, 'Write', user, md, [fileEntity.Name, input.Name]);
794
885
  const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
795
886
  await providerEntity.Load(fileEntity.ProviderID);
796
887
 
@@ -822,12 +913,14 @@ export class FileResolver extends FileResolverBase {
822
913
  }
823
914
  fileEntity.CheckPermissions(EntityPermissionType.Delete, true);
824
915
 
825
- // Only delete the object from the provider if it's actually been uploaded
916
+ // Only delete the object from the provider if it's actually been uploaded — after the storage gate: Write on its
917
+ // account, and no other row the caller cannot read may track the object (a row can alias another row's object)
826
918
  if (fileEntity.Status === 'Uploaded') {
919
+ await this.assertFileObjectAccess(fileEntity, 'Write', userInfo, md);
827
920
  const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', userInfo);
828
921
  await providerEntity.Load(fileEntity.ProviderID);
829
922
  const userContext = this.buildUserContext(context);
830
- await deleteObject(providerEntity, fileEntity.ProviderKey ?? fileEntity.Name, userContext);
923
+ await deleteObject(providerEntity, StorageObjectKeyOf(fileEntity), userContext);
831
924
  }
832
925
 
833
926
  return super.DeleteMJFile(ID, options, context, pubSub);
@@ -844,13 +937,16 @@ export class FileResolver extends FileResolverBase {
844
937
  const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
845
938
  const user = this.GetUserFromPayload(context.userPayload);
846
939
 
940
+ // Account gate first — before the account is loaded or any driver is touched
941
+ await this.assertAccountAccess(input.AccountID, 'Read', context);
942
+
847
943
  // Load the account and its provider
848
944
  const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
849
945
 
850
946
  console.log('[FileResolver] Provider loaded:', {
851
947
  Name: providerEntity.Name,
852
948
  ServerDriverKey: providerEntity.ServerDriverKey,
853
- HasConfiguration: !!providerEntity.Get('Configuration'),
949
+ HasConfiguration: !!providerEntity.Configuration,
854
950
  });
855
951
 
856
952
  // Check permissions - user must have read access to Files entity
@@ -868,8 +964,10 @@ export class FileResolver extends FileResolverBase {
868
964
  prefixes: result.prefixes,
869
965
  });
870
966
 
871
- // Convert Date objects to ISO strings for GraphQL
872
- const objects = result.objects.map((obj) => ({
967
+ // Drop objects behind an MJ: Files row the caller cannot read — a listing never names a tracked object the caller
968
+ // is refused — then convert Date objects to ISO strings for GraphQL
969
+ const visible = await this.dropUnreadableListedObjects(input.AccountID, result.objects, user, md);
970
+ const objects = visible.map((obj) => ({
873
971
  ...obj,
874
972
  lastModified: obj.lastModified.toISOString(),
875
973
  }));
@@ -889,6 +987,10 @@ export class FileResolver extends FileResolverBase {
889
987
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
890
988
  fileEntity.CheckPermissions(EntityPermissionType.Read, true);
891
989
 
990
+ // Account gate, then — when the object backs an MJ: Files row — that row must be readable by the caller
991
+ await this.assertAccountAccess(input.AccountID, 'Read', context);
992
+ await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
993
+
892
994
  await FileStorageEngine.Instance.Config(false, user, md);
893
995
  const driver = await FileStorageEngine.Instance.GetDriver(input.AccountID, user);
894
996
  const downloadUrl = await driver.CreatePreAuthDownloadUrl(input.ObjectName);
@@ -920,6 +1022,9 @@ export class FileResolver extends FileResolverBase {
920
1022
  // Check permissions
921
1023
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
922
1024
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
1025
+ await this.assertAccountAccess(input.AccountID, 'Write', context);
1026
+ // An upload URL can overwrite: the caller must be able to read any MJ: Files row that tracks the target
1027
+ await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
923
1028
 
924
1029
  await FileStorageEngine.Instance.Config(false, user, md);
925
1030
  const driver = await FileStorageEngine.Instance.GetDriver(input.AccountID, user);
@@ -956,7 +1061,7 @@ export class FileResolver extends FileResolverBase {
956
1061
  return {
957
1062
  Success: true,
958
1063
  Token: mint.Token,
959
- Url: '/media/upload-stage',
1064
+ Url: ApiRoute('/media/upload-stage'),
960
1065
  };
961
1066
  } catch (err) {
962
1067
  const message = err instanceof Error ? err.message : String(err);
@@ -972,12 +1077,16 @@ export class FileResolver extends FileResolverBase {
972
1077
  const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
973
1078
  const user = this.GetUserFromPayload(context.userPayload);
974
1079
 
1080
+ // Account gate first — before the account is loaded or any driver is touched
1081
+ await this.assertAccountAccess(input.AccountID, 'Write', context);
1082
+
975
1083
  // Load the account and its provider
976
1084
  const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
977
1085
 
978
1086
  // Check permissions
979
1087
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
980
1088
  fileEntity.CheckPermissions(EntityPermissionType.Delete, true);
1089
+ await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
981
1090
 
982
1091
  // Delete the object with extended user context (includes account for credential lookup)
983
1092
  const userContext = this.buildExtendedUserContext(context, account);
@@ -991,12 +1100,17 @@ export class FileResolver extends FileResolverBase {
991
1100
  const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
992
1101
  const user = this.GetUserFromPayload(context.userPayload);
993
1102
 
1103
+ // Account gate first — before the account is loaded or any driver is touched
1104
+ await this.assertAccountAccess(input.AccountID, 'Write', context);
1105
+
994
1106
  // Load the account and its provider
995
1107
  const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
996
1108
 
997
1109
  // Check permissions
998
1110
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
999
1111
  fileEntity.CheckPermissions(EntityPermissionType.Update, true);
1112
+ // A move reads and removes the source and may overwrite the destination: both get the tracked-file rule
1113
+ await this.assertTrackedObjectReadable(input.AccountID, [input.OldName, input.NewName], user, md);
1000
1114
 
1001
1115
  // Move the object with extended user context (includes account for credential lookup)
1002
1116
  const userContext = this.buildExtendedUserContext(context, account);
@@ -1009,6 +1123,9 @@ export class FileResolver extends FileResolverBase {
1009
1123
  const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
1010
1124
  const user = this.GetUserFromPayload(context.userPayload);
1011
1125
 
1126
+ // Account gate first — a copy within one account writes to it — before the account is loaded or any driver is touched
1127
+ await this.assertAccountAccess(input.AccountID, 'Write', context);
1128
+
1012
1129
  // Load the account and its provider
1013
1130
  const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
1014
1131
 
@@ -1016,6 +1133,9 @@ export class FileResolver extends FileResolverBase {
1016
1133
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
1017
1134
  fileEntity.CheckPermissions(EntityPermissionType.Read, true);
1018
1135
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
1136
+ // A copy of a tracked object is an untracked one the caller could then download, and the destination may be
1137
+ // overwritten: both get the tracked-file rule
1138
+ await this.assertTrackedObjectReadable(input.AccountID, [input.SourceName, input.DestinationName], user, md);
1019
1139
 
1020
1140
  // Copy the object with extended user context (includes account for credential lookup)
1021
1141
  const userContext = this.buildExtendedUserContext(context, account);
@@ -1028,6 +1148,9 @@ export class FileResolver extends FileResolverBase {
1028
1148
  const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
1029
1149
  const user = this.GetUserFromPayload(context.userPayload);
1030
1150
 
1151
+ // Account gate first — before the account is loaded or any driver is touched
1152
+ await this.assertAccountAccess(input.AccountID, 'Write', context);
1153
+
1031
1154
  // Load the account and its provider
1032
1155
  const { account: accountEntity, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
1033
1156
 
@@ -1063,6 +1186,13 @@ export class FileResolver extends FileResolverBase {
1063
1186
  fileEntity.CheckPermissions(EntityPermissionType.Read, true);
1064
1187
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
1065
1188
 
1189
+ // Account gates first: read the source, write the destination — before either account is loaded or touched
1190
+ await this.assertAccountAccess(input.SourceAccountID, 'Read', context);
1191
+ await this.assertAccountAccess(input.DestinationAccountID, 'Write', context);
1192
+ // Tracked-file rule on both ends: the source (its copy would be untracked), the destination (it may be overwritten)
1193
+ await this.assertTrackedObjectReadable(input.SourceAccountID, [input.SourcePath], user, md);
1194
+ await this.assertTrackedObjectReadable(input.DestinationAccountID, [input.DestinationPath], user, md);
1195
+
1066
1196
  // Load the source account and its provider
1067
1197
  const { account: sourceAccount, provider: sourceProviderEntity } = await this.loadAccountAndProvider(input.SourceAccountID, context);
1068
1198
 
@@ -1110,54 +1240,149 @@ export class FileResolver extends FileResolverBase {
1110
1240
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
1111
1241
  fileEntity.CheckPermissions(EntityPermissionType.Read, true);
1112
1242
 
1113
- // Use cached accounts from the engine — no RunView needed
1114
- await FileStorageEngine.Instance.Config(false, user);
1115
- const normalizedIDs = new Set(input.AccountIDs.map((id: string) => NormalizeUUID(id)));
1116
- const accountEntities = FileStorageEngine.Instance.Accounts
1117
- .filter(a => normalizedIDs.has(NormalizeUUID(a.ID)));
1243
+ // Account gate BEFORE any driver call: refused accounts are reported as failed and never searched
1244
+ const { searchable, refused } = await this.partitionSearchAccounts(input.AccountIDs, user, md);
1245
+ const result: MultiAccountSearchResult = searchable.length > 0
1246
+ ? await searchAcrossAccounts(searchable, input.Query, {
1247
+ maxResultsPerAccount: input.MaxResultsPerAccount,
1248
+ fileTypes: input.FileTypes,
1249
+ searchContent: input.SearchContent,
1250
+ contextUser: user,
1251
+ })
1252
+ : { accountResults: [], totalResultsReturned: 0, successfulAccounts: 0, failedAccounts: 0 };
1118
1253
 
1119
- if (accountEntities.length === 0) {
1120
- throw new Error('No valid storage accounts found for the provided IDs');
1121
- }
1254
+ console.log('[FileResolver] SearchAcrossAccounts result:', {
1255
+ totalResultsReturned: result.totalResultsReturned,
1256
+ successfulAccounts: result.successfulAccounts,
1257
+ failedAccounts: result.failedAccounts,
1258
+ refusedAccounts: refused.length,
1259
+ });
1260
+
1261
+ const kept = await this.dropUnreadableTrackedHits(result.accountResults, user, md);
1262
+ return this.toSearchAcrossAccountsPayload(this.inRequestOrder(input.AccountIDs, [...kept, ...refused]));
1263
+ }
1122
1264
 
1123
- // Log any accounts that weren't found
1124
- if (accountEntities.length < input.AccountIDs.length) {
1125
- const foundIDs = new Set(accountEntities.map((a) => NormalizeUUID(a.ID)));
1126
- const missingIDs = input.AccountIDs.filter((id) => !foundIDs.has(NormalizeUUID(id)));
1127
- console.warn(`[FileResolver] Accounts not found: ${missingIDs.join(', ')}`);
1265
+ /**
1266
+ * Per-account results in the order the accounts were requested (each requested ID once), whether searched or refused —
1267
+ * so a refused account sits where the caller put it rather than after every searched one.
1268
+ */
1269
+ private inRequestOrder(requestedIDs: string[], results: AccountSearchResult[]): AccountSearchResult[] {
1270
+ const byID = new Map(results.map((r) => [NormalizeUUID(r.accountID), r]));
1271
+ const ordered: AccountSearchResult[] = [];
1272
+ for (const id of requestedIDs) {
1273
+ const result = byID.get(NormalizeUUID(id));
1274
+ if (result) {
1275
+ ordered.push(result);
1276
+ byID.delete(NormalizeUUID(id));
1277
+ }
1128
1278
  }
1279
+ return [...ordered, ...byID.values()];
1280
+ }
1129
1281
 
1130
- // Load providers from cached metadata
1131
- const providerMap = new Map<string, MJFileStorageProviderEntity>();
1132
- for (const provider of FileStorageEngine.Instance.Providers) {
1133
- providerMap.set(provider.ID, provider);
1282
+ /**
1283
+ * Splits the requested accounts into those to search and those refused, before any driver call. An account the
1284
+ * caller may not read is refused with {@link SEARCH_ACCOUNT_REFUSED_MESSAGE} — and so is one that does not exist,
1285
+ * identically, so the response never reveals which IDs are real. Readable accounts are paired with their provider
1286
+ * from the engine's cache (refreshed once if a readable account is missing from it). Duplicate IDs are searched once.
1287
+ */
1288
+ private async partitionSearchAccounts(
1289
+ accountIDs: string[],
1290
+ user: UserInfo,
1291
+ md: IMetadataProvider,
1292
+ ): Promise<{ searchable: AccountSearchInput[]; refused: AccountSearchResult[] }> {
1293
+ const readable = await StorageAccessEvaluator.Instance.AccessibleAccountIDs(accountIDs, user, 'Read', md);
1294
+ await FileStorageEngine.Instance.Config(false, user, md);
1295
+ if ([...readable].some((id) => !FileStorageEngine.Instance.GetAccountWithProvider(id))) {
1296
+ await FileStorageEngine.Instance.RefreshAfterMiss(md);
1134
1297
  }
1135
1298
 
1136
- // Build account/provider pairs for the search
1137
- const accountInputs: AccountSearchInput[] = [];
1138
- for (const account of accountEntities) {
1139
- const provider = providerMap.get(account.ProviderID);
1140
- if (provider) {
1141
- accountInputs.push({ accountEntity: account, providerEntity: provider });
1299
+ const searchable: AccountSearchInput[] = [];
1300
+ const refused: AccountSearchResult[] = [];
1301
+ const seen = new Set<string>();
1302
+ for (const requestedID of accountIDs) {
1303
+ const key = NormalizeUUID(requestedID);
1304
+ if (seen.has(key)) continue;
1305
+ seen.add(key);
1306
+ const pair = readable.has(key) ? FileStorageEngine.Instance.GetAccountWithProvider(requestedID) : null;
1307
+ if (pair) {
1308
+ searchable.push({ accountEntity: pair.account, providerEntity: pair.provider });
1309
+ } else {
1310
+ refused.push(this.refusedAccountResult(requestedID));
1142
1311
  }
1143
1312
  }
1313
+ return { searchable, refused };
1314
+ }
1144
1315
 
1145
- // Execute the search across all accounts with account-based credentials
1146
- const result = await searchAcrossAccounts(accountInputs, input.Query, {
1147
- maxResultsPerAccount: input.MaxResultsPerAccount,
1148
- fileTypes: input.FileTypes,
1149
- searchContent: input.SearchContent,
1150
- contextUser: user,
1151
- });
1316
+ /** A failed per-account entry for an account the caller may not search; it names nothing beyond the requested ID. */
1317
+ private refusedAccountResult(accountID: string): AccountSearchResult {
1318
+ return {
1319
+ accountID,
1320
+ accountName: '',
1321
+ providerID: '',
1322
+ providerName: '',
1323
+ success: false,
1324
+ errorMessage: SEARCH_ACCOUNT_REFUSED_MESSAGE,
1325
+ results: [],
1326
+ hasMore: false,
1327
+ };
1328
+ }
1152
1329
 
1153
- console.log('[FileResolver] SearchAcrossAccounts result:', {
1154
- totalResultsReturned: result.totalResultsReturned,
1155
- successfulAccounts: result.successfulAccounts,
1156
- failedAccounts: result.failedAccounts,
1157
- });
1330
+ /**
1331
+ * Drops search hits that map to an `MJ: Files` row the caller may not read — one batched lookup per account, keyed on
1332
+ * each hit's path and provider object ID (`StorageAccessEvaluator.UnreadableTrackedObjectKeys`, compared the way the
1333
+ * account's driver addresses keys). Hits no row tracks stay; the account gate governs them.
1334
+ *
1335
+ * When any hit is dropped, the provider's `totalMatches` (which counted it) and `nextPageToken` (this route takes no
1336
+ * page token back) are withheld; `hasMore` is kept. How reliable the mapping is depends on the provider: for
1337
+ * object-key stores (S3, Azure Blob, GCS) a hit's path IS the row's `ProviderKey`; for hierarchical providers (Box,
1338
+ * Google Drive, Dropbox, SharePoint) a search path may not match the key a row was written with, and such a miss falls
1339
+ * back to the account gate. `MJ: Files` rows are keyed by provider, not account, so the lookup errs toward dropping.
1340
+ */
1341
+ private async dropUnreadableTrackedHits(
1342
+ accountResults: AccountSearchResult[],
1343
+ user: UserInfo,
1344
+ md: IMetadataProvider,
1345
+ ): Promise<AccountSearchResult[]> {
1346
+ return Promise.all(accountResults.map((ar) => this.dropUnreadableAccountHits(ar, user, md)));
1347
+ }
1158
1348
 
1159
- // Convert results to GraphQL payload format
1160
- const accountResults: AccountSearchResultPayload[] = result.accountResults.map((ar: AccountSearchResult) => ({
1349
+ /** {@link dropUnreadableTrackedHits} for one account's results; if the check cannot run, the account's hits are dropped. */
1350
+ private async dropUnreadableAccountHits(ar: AccountSearchResult, user: UserInfo, md: IMetadataProvider): Promise<AccountSearchResult> {
1351
+ if (!ar.success || ar.results.length === 0) return ar;
1352
+ try {
1353
+ const driver = await FileStorageEngine.Instance.GetDriver(ar.accountID, user);
1354
+ const keys = ar.results.flatMap((r) => [r.path, r.objectId].filter((k): k is string => !!k));
1355
+ const unreadable = await StorageAccessEvaluator.Instance.UnreadableTrackedObjectKeys(ar.providerID, keys, user, md, driver);
1356
+ const results = ar.results.filter((r) => !unreadable.has(r.path) && !(r.objectId && unreadable.has(r.objectId)));
1357
+ return results.length === ar.results.length ? ar : { ...ar, results, totalMatches: undefined, nextPageToken: undefined };
1358
+ } catch (error) {
1359
+ const reason = error instanceof Error ? error.message : String(error);
1360
+ LogError(`SearchAcrossAccounts: tracked-file check failed for account ${ar.accountID} — its hits are withheld: ${reason}`);
1361
+ return { ...ar, results: [], totalMatches: undefined, nextPageToken: undefined };
1362
+ }
1363
+ }
1364
+
1365
+ /**
1366
+ * The listed objects minus those behind an `MJ: Files` row the caller may not read (directories are prefixes, never
1367
+ * tracked objects, and are kept). Keys are compared the way the account's driver addresses them.
1368
+ */
1369
+ private async dropUnreadableListedObjects(
1370
+ accountId: string,
1371
+ objects: StoredObjectMetadata[],
1372
+ user: UserInfo,
1373
+ md: IMetadataProvider,
1374
+ ): Promise<StoredObjectMetadata[]> {
1375
+ const files = objects.filter((o) => !o.isDirectory);
1376
+ if (files.length === 0) return objects;
1377
+ const providerID = await this.accountProviderID(accountId, user, md);
1378
+ const driver = await FileStorageEngine.Instance.GetDriver(accountId, user);
1379
+ const unreadable = await StorageAccessEvaluator.Instance.UnreadableTrackedObjectKeys(providerID, files.map((o) => o.fullPath), user, md, driver);
1380
+ return objects.filter((o) => o.isDirectory || !unreadable.has(o.fullPath));
1381
+ }
1382
+
1383
+ /** Converts per-account results to the GraphQL payload, recomputing the totals from what is actually returned. */
1384
+ private toSearchAcrossAccountsPayload(accountResults: AccountSearchResult[]): SearchAcrossAccountsPayload {
1385
+ const payloadResults: AccountSearchResultPayload[] = accountResults.map((ar: AccountSearchResult) => ({
1161
1386
  accountID: ar.accountID,
1162
1387
  accountName: ar.accountName,
1163
1388
  success: ar.success,
@@ -1179,10 +1404,10 @@ export class FileResolver extends FileResolverBase {
1179
1404
  }));
1180
1405
 
1181
1406
  return {
1182
- accountResults,
1183
- totalResultsReturned: result.totalResultsReturned,
1184
- successfulAccounts: result.successfulAccounts,
1185
- failedAccounts: result.failedAccounts,
1407
+ accountResults: payloadResults,
1408
+ totalResultsReturned: accountResults.reduce((sum, ar) => sum + (ar.success ? ar.results.length : 0), 0),
1409
+ successfulAccounts: accountResults.filter((ar) => ar.success).length,
1410
+ failedAccounts: accountResults.filter((ar) => !ar.success).length,
1186
1411
  };
1187
1412
  }
1188
1413
  }