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