@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
@@ -0,0 +1,633 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
3
+ import type { IdentityVerifiedEventPayload } from '@memberjunction/ai-core-plus';
4
+ import {
5
+ RealtimeSessionVerificationWorkflow,
6
+ type ConfigMutation,
7
+ type ConfigMutationResult,
8
+ type OutboundEmail,
9
+ type PrincipalCaller,
10
+ type SessionSnapshot,
11
+ type VerificationAuditEntry,
12
+ type VerificationCaller,
13
+ type VerificationPorts,
14
+ type VerificationSettings,
15
+ } from '../../realtimeSessions/verificationWorkflow.js';
16
+ import { parseConfigObjectForTest } from './helpers.js';
17
+
18
+ const OWNER = 'AAAAAAAA-0000-4000-8000-000000000001';
19
+ const STRANGER = 'AAAAAAAA-0000-4000-8000-000000000002';
20
+ const SESSION_ID = 'BBBBBBBB-0000-4000-8000-000000000001';
21
+ const OTHER_SESSION_ID = 'BBBBBBBB-0000-4000-8000-000000000002';
22
+ const T0 = Date.parse('2026-10-02T12:00:00.000Z');
23
+
24
+ /** In-memory ports that behave like the real ones: a session store with read-modify-write, an outbox, an event log, an audit log. */
25
+ class FakePorts implements VerificationPorts {
26
+ public Now_ = T0;
27
+ public Settings: VerificationSettings = {
28
+ Enabled: true,
29
+ PolicyDefaults: {},
30
+ PublicBaseUrl: 'https://api.example.com',
31
+ CodeHashKey: 'unit-test-code-hash-key-0123456789',
32
+ RateLimits: { SendWindowMs: 3_600_000, PerIpSends: 100, PerEmailDomainSends: 100, PerEmailSends: 100, CodeAttemptWindowMs: 600_000, PerSessionCodeAttempts: 100 },
33
+ };
34
+ public Sessions = new Map<string, SessionSnapshot>();
35
+ public Emails: OutboundEmail[] = [];
36
+ public EmailResult: { Success: boolean; ErrorMessage?: string } = { Success: true };
37
+ public Published: Array<{ routing: { AgentSessionID: string; OwnerUserID: string; ScopeKey: string | null }; payload: IdentityVerifiedEventPayload }> = [];
38
+ public Audits: VerificationAuditEntry[] = [];
39
+ public PersistFails = false;
40
+ public Writes = 0;
41
+ /** When true, a write yields to the event loop between reading the session and persisting — like a real database round trip. */
42
+ public Latency = false;
43
+
44
+ public GetSettings(): VerificationSettings {
45
+ return this.Settings;
46
+ }
47
+
48
+ public Now(): number {
49
+ return this.Now_;
50
+ }
51
+
52
+ public async LoadSession(id: string): Promise<SessionSnapshot | null> {
53
+ const found = this.Sessions.get(id.toLowerCase());
54
+ return found ? { ...found } : null;
55
+ }
56
+
57
+ public async MutateSessionConfig<T>(id: string, _caller: VerificationCaller, mutate: ConfigMutation<T>): Promise<ConfigMutationResult<T>> {
58
+ const stored = this.Sessions.get(id.toLowerCase());
59
+ if (!stored) {
60
+ return { Persisted: false, ErrorMessage: 'not found' };
61
+ }
62
+ const session = { ...stored };
63
+ const { NextConfigRaw, Outcome } = mutate(session);
64
+ if (this.Latency) {
65
+ await new Promise<void>((resolve) => setImmediate(resolve));
66
+ }
67
+ if (NextConfigRaw === null) {
68
+ return { Session: session, Outcome, Persisted: false };
69
+ }
70
+ if (this.PersistFails) {
71
+ return { Session: session, Outcome, Persisted: false, ErrorMessage: 'disk full' };
72
+ }
73
+ stored.ConfigRaw = NextConfigRaw;
74
+ this.Writes++;
75
+ return { Session: session, Outcome, Persisted: true };
76
+ }
77
+
78
+ public async SendEmail(email: OutboundEmail): Promise<{ Success: boolean; ErrorMessage?: string }> {
79
+ if (this.EmailResult.Success) {
80
+ this.Emails.push(email);
81
+ }
82
+ return this.EmailResult;
83
+ }
84
+
85
+ public PublishVerified(routing: { AgentSessionID: string; OwnerUserID: string; ScopeKey: string | null }, payload: IdentityVerifiedEventPayload) {
86
+ this.Published.push({ routing, payload });
87
+ return { Success: true };
88
+ }
89
+
90
+ public async WriteAudit(entry: VerificationAuditEntry): Promise<void> {
91
+ this.Audits.push(entry);
92
+ }
93
+
94
+ public AddSession(over: Partial<SessionSnapshot> = {}): SessionSnapshot {
95
+ const session: SessionSnapshot = {
96
+ AgentSessionID: SESSION_ID,
97
+ OwnerUserID: OWNER,
98
+ ConversationExternalID: null,
99
+ Status: 'Active',
100
+ ConfigRaw: JSON.stringify({ targetAgentID: 'target-1' }),
101
+ StartedAtMs: T0,
102
+ ...over,
103
+ };
104
+ this.Sessions.set(session.AgentSessionID.toLowerCase(), session);
105
+ return session;
106
+ }
107
+
108
+ public Config(id = SESSION_ID): Record<string, unknown> {
109
+ return parseConfigObjectForTest(this.Sessions.get(id.toLowerCase())?.ConfigRaw ?? null);
110
+ }
111
+ }
112
+
113
+ const principal = (over: Partial<UserInfo> = {}, ClientIp = '203.0.113.7'): PrincipalCaller => ({
114
+ Kind: 'principal',
115
+ ContextUser: { ID: OWNER, ...over } as UserInfo,
116
+ Provider: {} as IMetadataProvider,
117
+ ClientIp,
118
+ });
119
+
120
+ const REQUEST = { AgentSessionID: SESSION_ID, Name: 'Pat Smith', Email: 'Pat@Acme.com' };
121
+
122
+ /** Pulls the link token and typed code out of the last verification email. */
123
+ function secretsFrom(ports: FakePorts): { token: string; code: string } {
124
+ const email = ports.Emails[ports.Emails.length - 1];
125
+ const token = /mj_rv_[0-9a-f]{32}_[0-9a-f]{64}/.exec(email.Text)?.[0];
126
+ const code = /\n(\d{6})\n/.exec(email.Text)?.[1];
127
+ if (!token || !code) throw new Error('no secrets in email');
128
+ return { token, code };
129
+ }
130
+
131
+ describe('RealtimeSessionVerificationWorkflow', () => {
132
+ let ports: FakePorts;
133
+ let workflow: RealtimeSessionVerificationWorkflow;
134
+
135
+ const rebuild = () => {
136
+ workflow = new RealtimeSessionVerificationWorkflow(ports);
137
+ };
138
+
139
+ beforeEach(() => {
140
+ ports = new FakePorts();
141
+ ports.AddSession();
142
+ rebuild();
143
+ });
144
+
145
+ describe('RequestVerification — success path', () => {
146
+ it('stores a pending verification (hashes only), emails a link and a code, and reports what is left', async () => {
147
+ const result = await workflow.RequestVerification(REQUEST, principal());
148
+ expect(result).toMatchObject({ Success: true, VerificationState: 'pending', SendsRemaining: 2 });
149
+ expect(Date.parse(result.ExpiresAt as string)).toBe(T0 + 30 * 60_000);
150
+
151
+ expect(ports.Emails).toHaveLength(1);
152
+ expect(ports.Emails[0].To).toBe('pat@acme.com');
153
+ const { token, code } = secretsFrom(ports);
154
+ expect(ports.Emails[0].Text).toContain(`https://api.example.com/realtime/verify/${token}`);
155
+ expect(ports.Emails[0].Html).toContain(code);
156
+
157
+ const stored = JSON.stringify(ports.Config());
158
+ expect(stored).not.toContain(token);
159
+ expect(stored).not.toContain(code);
160
+ expect(ports.Config()['identityVerification']).toMatchObject({ SendCount: 1, Pending: { Email: 'pat@acme.com', Name: 'Pat Smith' } });
161
+ expect(ports.Config()['targetAgentID']).toBe('target-1'); // other keys preserved
162
+ });
163
+
164
+ it('never reveals the verified state before redemption: nothing is published or audited on request', async () => {
165
+ await workflow.RequestVerification(REQUEST, principal());
166
+ expect(ports.Published).toHaveLength(0);
167
+ expect(ports.Audits).toHaveLength(0);
168
+ });
169
+
170
+ it('uses the TTL from policy', async () => {
171
+ ports.Settings.PolicyDefaults = { linkTtlMinutes: 5 };
172
+ const result = await workflow.RequestVerification(REQUEST, principal());
173
+ expect(Date.parse(result.ExpiresAt as string)).toBe(T0 + 5 * 60_000);
174
+ expect(ports.Emails[0].Text).toContain('expire in 5 minutes');
175
+ });
176
+
177
+ it('replaces an earlier pending verification (resend) and consumes another send', async () => {
178
+ await workflow.RequestVerification(REQUEST, principal());
179
+ const first = secretsFrom(ports);
180
+ await workflow.RequestVerification(REQUEST, principal());
181
+ const second = secretsFrom(ports);
182
+ expect(second.token).not.toBe(first.token);
183
+ expect((ports.Config()['identityVerification'] as { SendCount: number }).SendCount).toBe(2);
184
+ // The superseded link is dead; the new one works.
185
+ expect(await workflow.RedeemLink(first.token, { Kind: 'link' })).toMatchObject({ Success: false, ErrorCode: 'invalid_link' });
186
+ expect(await workflow.RedeemLink(second.token, { Kind: 'link' })).toMatchObject({ Success: true });
187
+ });
188
+ });
189
+
190
+ describe('RequestVerification — refusals', () => {
191
+ it('refuses when the feature is disabled or cannot build a link', async () => {
192
+ ports.Settings.Enabled = false;
193
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ Success: false, ErrorCode: 'verification_unavailable' });
194
+ ports.Settings.Enabled = true;
195
+ ports.Settings.PublicBaseUrl = undefined;
196
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ ErrorCode: 'verification_unavailable' });
197
+ expect(ports.Emails).toHaveLength(0);
198
+ });
199
+
200
+ it('refuses a blank name and a malformed email before touching the session', async () => {
201
+ expect(await workflow.RequestVerification({ ...REQUEST, Name: ' ' }, principal())).toMatchObject({ ErrorCode: 'invalid_input' });
202
+ expect(await workflow.RequestVerification({ ...REQUEST, Email: 'not an email' }, principal())).toMatchObject({ ErrorCode: 'invalid_email' });
203
+ expect(ports.Writes).toBe(0);
204
+ });
205
+
206
+ it('refuses an unknown session and a malformed session id identically', async () => {
207
+ expect(await workflow.RequestVerification({ ...REQUEST, AgentSessionID: OTHER_SESSION_ID }, principal())).toMatchObject({ ErrorCode: 'session_not_found' });
208
+ expect(await workflow.RequestVerification({ ...REQUEST, AgentSessionID: 'nope' }, principal())).toMatchObject({ ErrorCode: 'session_not_found' });
209
+ });
210
+
211
+ it('refuses a non-owner with the same result as a missing session (no enumeration)', async () => {
212
+ const stranger = await workflow.RequestVerification(REQUEST, principal({ ID: STRANGER }));
213
+ const missing = await workflow.RequestVerification({ ...REQUEST, AgentSessionID: OTHER_SESSION_ID }, principal());
214
+ expect(stranger).toEqual(missing);
215
+ expect(ports.Emails).toHaveLength(0);
216
+ expect(ports.Writes).toBe(0);
217
+ });
218
+
219
+ it('refuses another anonymous guest sharing the Anonymous user (scope mismatch) and a scope-less anonymous caller', async () => {
220
+ ports.AddSession({ ConversationExternalID: 'scope-A' });
221
+ expect(
222
+ await workflow.RequestVerification(REQUEST, principal({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'scope-B' } })),
223
+ ).toMatchObject({ ErrorCode: 'session_not_found' });
224
+ expect(await workflow.RequestVerification(REQUEST, principal({ IsMagicLinkAnonymous: true }))).toMatchObject({ ErrorCode: 'session_not_found' });
225
+ expect(
226
+ await workflow.RequestVerification(REQUEST, principal({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'scope-A' } })),
227
+ ).toMatchObject({ Success: true });
228
+ });
229
+
230
+ it('refuses a closed session', async () => {
231
+ ports.AddSession({ Status: 'Closed' });
232
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ ErrorCode: 'session_closed' });
233
+ });
234
+
235
+ it('treats an already-verified session as success without sending anything', async () => {
236
+ await workflow.RequestVerification(REQUEST, principal());
237
+ const { code } = secretsFrom(ports);
238
+ await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal());
239
+ ports.Emails.length = 0;
240
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ Success: true, VerificationState: 'verified', VerifiedEmail: 'pat@acme.com' });
241
+ expect(ports.Emails).toHaveLength(0);
242
+ });
243
+ });
244
+
245
+ describe('RequestVerification — email domain policy', () => {
246
+ it('requireBusinessDomain refuses consumer domains and allows business ones', async () => {
247
+ ports.Settings.PolicyDefaults = { requireBusinessDomain: true };
248
+ expect(await workflow.RequestVerification({ ...REQUEST, Email: 'pat@gmail.com' }, principal())).toMatchObject({ Success: false, ErrorCode: 'consumer_domain' });
249
+ expect(await workflow.RequestVerification({ ...REQUEST, Email: 'pat@yahoo.co.uk' }, principal())).toMatchObject({ ErrorCode: 'consumer_domain' });
250
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ Success: true });
251
+ expect(ports.Emails).toHaveLength(1); // only the business address was ever mailed
252
+ });
253
+
254
+ it('blockedDomains refuses even business addresses', async () => {
255
+ ports.Settings.PolicyDefaults = { blockedDomains: ['acme.com'] };
256
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ ErrorCode: 'domain_blocked' });
257
+ });
258
+
259
+ it('the per-session policy snapshot (from the cascade at mint) overrides server defaults, and the client cannot supply one', async () => {
260
+ ports.Settings.PolicyDefaults = { requireBusinessDomain: false };
261
+ ports.AddSession({ ConfigRaw: JSON.stringify({ identityVerification: { SendCount: 0, Policy: { requireBusinessDomain: true } } }) });
262
+ expect(await workflow.RequestVerification({ ...REQUEST, Email: 'pat@gmail.com' }, principal())).toMatchObject({ ErrorCode: 'consumer_domain' });
263
+ });
264
+
265
+ it('honours a custom consumer-domain list', async () => {
266
+ ports.Settings.PolicyDefaults = { requireBusinessDomain: true, consumerDomains: ['freemail.example'] };
267
+ expect(await workflow.RequestVerification({ ...REQUEST, Email: 'pat@freemail.example' }, principal())).toMatchObject({ ErrorCode: 'consumer_domain' });
268
+ expect(await workflow.RequestVerification({ ...REQUEST, Email: 'pat@gmail.com' }, principal())).toMatchObject({ Success: true });
269
+ });
270
+ });
271
+
272
+ describe('RequestVerification — send limits', () => {
273
+ it('maxSendsPerSession caps emails per session and survives re-creating the workflow (persisted, not in memory)', async () => {
274
+ ports.Settings.PolicyDefaults = { maxSendsPerSession: 2 };
275
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ Success: true, SendsRemaining: 1 });
276
+ rebuild(); // a different replica / a restart
277
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ Success: true, SendsRemaining: 0 });
278
+ rebuild();
279
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ Success: false, ErrorCode: 'send_limit_reached', SendsRemaining: 0 });
280
+ expect(ports.Emails).toHaveLength(2);
281
+ });
282
+
283
+ it('rate limits per client IP', async () => {
284
+ ports.Settings.RateLimits.PerIpSends = 2;
285
+ rebuild();
286
+ expect((await workflow.RequestVerification({ ...REQUEST, Email: 'a@one.com' }, principal({}, '198.51.100.1'))).Success).toBe(true);
287
+ expect((await workflow.RequestVerification({ ...REQUEST, Email: 'b@two.com' }, principal({}, '198.51.100.1'))).Success).toBe(true);
288
+ const limited = await workflow.RequestVerification({ ...REQUEST, Email: 'c@three.com' }, principal({}, '198.51.100.1'));
289
+ expect(limited).toMatchObject({ Success: false, ErrorCode: 'rate_limited' });
290
+ expect(limited.RetryAfterSeconds).toBeGreaterThan(0);
291
+ // A different IP has its own budget.
292
+ expect((await workflow.RequestVerification({ ...REQUEST, Email: 'c@three.com' }, principal({}, '198.51.100.2'))).Success).toBe(true);
293
+ });
294
+
295
+ it('rate limits per recipient email domain', async () => {
296
+ ports.Settings.RateLimits.PerEmailDomainSends = 2;
297
+ ports.Settings.PolicyDefaults = { maxSendsPerSession: 10 };
298
+ rebuild();
299
+ expect((await workflow.RequestVerification({ ...REQUEST, Email: 'a@acme.com' }, principal({}, '10.0.0.1'))).Success).toBe(true);
300
+ expect((await workflow.RequestVerification({ ...REQUEST, Email: 'b@acme.com' }, principal({}, '10.0.0.2'))).Success).toBe(true);
301
+ expect(await workflow.RequestVerification({ ...REQUEST, Email: 'c@acme.com' }, principal({}, '10.0.0.3'))).toMatchObject({ ErrorCode: 'rate_limited' });
302
+ expect((await workflow.RequestVerification({ ...REQUEST, Email: 'a@other.com' }, principal({}, '10.0.0.4'))).Success).toBe(true);
303
+ });
304
+
305
+ it('rate limits per recipient address (no mail-bombing one inbox through many sessions)', async () => {
306
+ ports.Settings.RateLimits.PerEmailSends = 1;
307
+ ports.Settings.PolicyDefaults = { maxSendsPerSession: 10 };
308
+ rebuild();
309
+ expect((await workflow.RequestVerification(REQUEST, principal({}, '10.0.0.1'))).Success).toBe(true);
310
+ ports.AddSession({ AgentSessionID: OTHER_SESSION_ID });
311
+ expect(await workflow.RequestVerification({ ...REQUEST, AgentSessionID: OTHER_SESSION_ID }, principal({}, '10.0.0.2'))).toMatchObject({ ErrorCode: 'rate_limited' });
312
+ expect(ports.Emails).toHaveLength(1);
313
+ });
314
+
315
+ it('treats a zero limit as disabled', async () => {
316
+ ports.Settings.RateLimits = { ...ports.Settings.RateLimits, PerIpSends: 0, PerEmailDomainSends: 0, PerEmailSends: 0 };
317
+ ports.Settings.PolicyDefaults = { maxSendsPerSession: 10 };
318
+ rebuild();
319
+ for (let i = 0; i < 5; i++) {
320
+ expect((await workflow.RequestVerification(REQUEST, principal())).Success).toBe(true);
321
+ }
322
+ });
323
+ });
324
+
325
+ describe('RequestVerification — delivery failure', () => {
326
+ it('rolls the pending verification and the send count back, so nothing is consumed and no secret stays live', async () => {
327
+ ports.EmailResult = { Success: false, ErrorMessage: 'provider 500' };
328
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ Success: false, ErrorCode: 'email_send_failed' });
329
+ const state = ports.Config()['identityVerification'] as { SendCount: number; Pending?: unknown };
330
+ expect(state.SendCount).toBe(0);
331
+ expect(state.Pending).toBeUndefined();
332
+ });
333
+
334
+ it('fails with persist_failed (and sends nothing) when the pending state cannot be stored', async () => {
335
+ ports.PersistFails = true;
336
+ expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ Success: false, ErrorCode: 'persist_failed' });
337
+ expect(ports.Emails).toHaveLength(0);
338
+ });
339
+ });
340
+
341
+ describe('SubmitCode', () => {
342
+ async function requested(): Promise<{ token: string; code: string }> {
343
+ await workflow.RequestVerification(REQUEST, principal());
344
+ return secretsFrom(ports);
345
+ }
346
+
347
+ it('verifies, records the identity, publishes identity.verified, audits, and never changes the principal', async () => {
348
+ const { code } = await requested();
349
+ ports.Now_ = T0 + 60_000;
350
+ const result = await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: ` ${code.slice(0, 3)} ${code.slice(3)} ` }, principal());
351
+ expect(result).toMatchObject({ Success: true, VerificationState: 'verified', VerifiedEmail: 'pat@acme.com' });
352
+
353
+ expect(ports.Config()['identityVerification']).toMatchObject({ Verified: { Email: 'pat@acme.com', Name: 'Pat Smith', Method: 'code' } });
354
+ expect((ports.Config()['identityVerification'] as { Pending?: unknown }).Pending).toBeUndefined();
355
+
356
+ expect(ports.Published).toHaveLength(1);
357
+ expect(ports.Published[0].routing).toEqual({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, ScopeKey: null });
358
+ expect(ports.Published[0].payload).toMatchObject({ VerifiedEmail: 'pat@acme.com', VerifiedName: 'Pat Smith', Method: 'code' });
359
+ expect(ports.Audits).toEqual([expect.objectContaining({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, Email: 'pat@acme.com', Method: 'code', ClientIp: '203.0.113.7' })]);
360
+ expect(ports.Sessions.get(SESSION_ID.toLowerCase())?.OwnerUserID).toBe(OWNER);
361
+ });
362
+
363
+ it('routes the event with the session scope so an anonymous guest\'s event reaches only that guest', async () => {
364
+ ports.AddSession({ ConversationExternalID: 'scope-A' });
365
+ const guest = principal({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'scope-A' } });
366
+ await workflow.RequestVerification(REQUEST, guest);
367
+ await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: secretsFrom(ports).code }, guest);
368
+ expect(ports.Published[0].routing.ScopeKey).toBe('scope-A');
369
+ });
370
+
371
+ it('is single-use: the same code does not verify twice and publishes once', async () => {
372
+ const { code } = await requested();
373
+ await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal());
374
+ const again = await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal());
375
+ expect(again).toMatchObject({ Success: true, VerificationState: 'verified' }); // idempotent for the caller...
376
+ expect(ports.Published).toHaveLength(1); // ...but nothing is re-announced
377
+ expect(ports.Audits).toHaveLength(1);
378
+ });
379
+
380
+ it('two simultaneous submissions of the right code verify exactly once (even with database latency)', async () => {
381
+ const { code } = await requested();
382
+ ports.Latency = true;
383
+ const results = await Promise.all([
384
+ workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal()),
385
+ workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal()),
386
+ ]);
387
+ expect(results.every((r) => r.Success)).toBe(true);
388
+ expect(ports.Published).toHaveLength(1);
389
+ });
390
+
391
+ it('counts wrong codes, reports attempts left, and voids the verification at the cap', async () => {
392
+ ports.Settings.PolicyDefaults = { maxCodeAttempts: 3 };
393
+ const { code } = await requested();
394
+ const wrong = code === '000000' ? '111111' : '000000';
395
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: wrong }, principal())).toMatchObject({ ErrorCode: 'invalid_code', AttemptsRemaining: 2, VerificationState: 'pending' });
396
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: wrong }, principal())).toMatchObject({ ErrorCode: 'invalid_code', AttemptsRemaining: 1 });
397
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: wrong }, principal())).toMatchObject({ ErrorCode: 'attempts_exhausted' });
398
+ // The right code no longer works: a new email is required.
399
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal())).toMatchObject({ ErrorCode: 'no_pending_verification' });
400
+ expect(ports.Published).toHaveLength(0);
401
+ });
402
+
403
+ it('counts attempts across replicas (the counter is persisted on the session)', async () => {
404
+ ports.Settings.PolicyDefaults = { maxCodeAttempts: 2 };
405
+ const { code } = await requested();
406
+ const wrong = code === '000000' ? '111111' : '000000';
407
+ await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: wrong }, principal());
408
+ rebuild();
409
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: wrong }, principal())).toMatchObject({ ErrorCode: 'attempts_exhausted' });
410
+ });
411
+
412
+ it('a malformed code is refused without burning an attempt', async () => {
413
+ const { code } = await requested();
414
+ for (const bad of ['12345', 'abcdef', '', '1234567']) {
415
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: bad }, principal())).toMatchObject({ ErrorCode: 'invalid_code' });
416
+ }
417
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal())).toMatchObject({ Success: true });
418
+ });
419
+
420
+ it('rejects an expired code', async () => {
421
+ const { code } = await requested();
422
+ ports.Now_ = T0 + 31 * 60_000;
423
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal())).toMatchObject({ ErrorCode: 'code_expired' });
424
+ expect(ports.Published).toHaveLength(0);
425
+ });
426
+
427
+ it('says so when nothing was requested', async () => {
428
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: '123456' }, principal())).toMatchObject({ ErrorCode: 'no_pending_verification' });
429
+ });
430
+
431
+ it('rate limits submissions per session before any state is read', async () => {
432
+ ports.Settings.RateLimits.PerSessionCodeAttempts = 2;
433
+ rebuild();
434
+ await workflow.RequestVerification(REQUEST, principal());
435
+ const bad = { AgentSessionID: SESSION_ID, Code: '000000' };
436
+ await workflow.SubmitCode(bad, principal());
437
+ await workflow.SubmitCode(bad, principal());
438
+ const limited = await workflow.SubmitCode(bad, principal());
439
+ expect(limited).toMatchObject({ ErrorCode: 'rate_limited' });
440
+ expect(limited.RetryAfterSeconds).toBeGreaterThan(0);
441
+ });
442
+
443
+ it('refuses a non-owner and an anonymous guest of another scope', async () => {
444
+ const { code } = await requested();
445
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal({ ID: STRANGER }))).toMatchObject({ ErrorCode: 'session_not_found' });
446
+ ports.AddSession({ ConversationExternalID: 'scope-A', ConfigRaw: ports.Sessions.get(SESSION_ID.toLowerCase())?.ConfigRaw ?? null });
447
+ expect(
448
+ await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'scope-B' } })),
449
+ ).toMatchObject({ ErrorCode: 'session_not_found' });
450
+ expect(ports.Published).toHaveLength(0);
451
+ });
452
+
453
+ it('refuses a closed session', async () => {
454
+ const { code } = await requested();
455
+ ports.Sessions.get(SESSION_ID.toLowerCase())!.Status = 'Closed';
456
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal())).toMatchObject({ ErrorCode: 'session_closed' });
457
+ });
458
+
459
+ it('reports persist_failed (and publishes nothing) when the verified state cannot be recorded', async () => {
460
+ const { code } = await requested();
461
+ ports.PersistFails = true;
462
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal())).toMatchObject({ ErrorCode: 'persist_failed' });
463
+ expect(ports.Published).toHaveLength(0);
464
+ expect(ports.Audits).toHaveLength(0);
465
+ });
466
+
467
+ it('is unavailable when the feature is off', async () => {
468
+ ports.Settings.Enabled = false;
469
+ expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: '123456' }, principal())).toMatchObject({ ErrorCode: 'verification_unavailable' });
470
+ });
471
+ });
472
+
473
+ describe('RedeemLink', () => {
474
+ const link: Extract<VerificationCaller, { Kind: 'link' }> = { Kind: 'link', ClientIp: '192.0.2.9' };
475
+
476
+ async function requestedToken(): Promise<string> {
477
+ await workflow.RequestVerification(REQUEST, principal());
478
+ return secretsFrom(ports).token;
479
+ }
480
+
481
+ it('verifies from a different device with no identity at all, publishes the event and audits', async () => {
482
+ const token = await requestedToken();
483
+ const result = await workflow.RedeemLink(token, link);
484
+ expect(result).toMatchObject({ Success: true, VerificationState: 'verified', VerifiedEmail: 'pat@acme.com' });
485
+ expect(ports.Published[0].payload).toMatchObject({ Method: 'link', VerifiedEmail: 'pat@acme.com' });
486
+ expect(ports.Audits[0]).toMatchObject({ Method: 'link', ClientIp: '192.0.2.9' });
487
+ });
488
+
489
+ it('is single-use', async () => {
490
+ const token = await requestedToken();
491
+ expect((await workflow.RedeemLink(token, link)).Success).toBe(true);
492
+ expect(await workflow.RedeemLink(token, link)).toMatchObject({ Success: false, ErrorCode: 'invalid_link' });
493
+ expect(ports.Published).toHaveLength(1);
494
+ });
495
+
496
+ it('two simultaneous redemptions of the same link verify exactly once (even with database latency)', async () => {
497
+ const token = await requestedToken();
498
+ ports.Latency = true;
499
+ const results = await Promise.all([workflow.RedeemLink(token, link), workflow.RedeemLink(token, link)]);
500
+ expect(results.filter((r) => r.Success)).toHaveLength(1);
501
+ expect(ports.Published).toHaveLength(1);
502
+ });
503
+
504
+ it('a typed code and a link are one verification: whichever is used first wins and kills the other', async () => {
505
+ const token = await requestedToken();
506
+ const { code } = secretsFrom(ports);
507
+ await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal());
508
+ expect(await workflow.RedeemLink(token, link)).toMatchObject({ Success: false, ErrorCode: 'invalid_link' });
509
+ });
510
+
511
+ it('gives ONE uniform failure for malformed, unknown-session, wrong-secret, expired and used tokens', async () => {
512
+ const token = await requestedToken();
513
+ const forOtherSession = token.replace(SESSION_ID.replace(/-/g, '').toLowerCase(), OTHER_SESSION_ID.replace(/-/g, '').toLowerCase());
514
+ const wrongSecret = token.slice(0, -4) + (token.endsWith('0000') ? '1111' : '0000');
515
+ const outcomes = [
516
+ await workflow.RedeemLink('garbage', link),
517
+ await workflow.RedeemLink(forOtherSession, link), // session does not exist
518
+ await workflow.RedeemLink(wrongSecret, link),
519
+ ];
520
+ ports.Now_ = T0 + 31 * 60_000;
521
+ outcomes.push(await workflow.RedeemLink(token, link)); // expired
522
+ for (const outcome of outcomes) {
523
+ expect(outcome).toEqual({ Success: false, VerificationState: 'unverified', ErrorCode: 'invalid_link', Message: 'This link is no longer valid.' });
524
+ }
525
+ });
526
+
527
+ it('does not let a wrong token burn the pending verification', async () => {
528
+ const token = await requestedToken();
529
+ await workflow.RedeemLink(token.slice(0, -4) + (token.endsWith('0000') ? '1111' : '0000'), link);
530
+ expect((await workflow.RedeemLink(token, link)).Success).toBe(true);
531
+ });
532
+
533
+ it('records the verification on a CLOSED session (resume after the cap) but announces and extends nothing', async () => {
534
+ ports.Settings.PolicyDefaults = { verifiedMaxSeconds: 3600 };
535
+ ports.AddSession({ ConfigRaw: JSON.stringify({ maxSessionDeadlineIso: new Date(T0 + 300_000).toISOString() }) });
536
+ const token = await requestedToken();
537
+ ports.Sessions.get(SESSION_ID.toLowerCase())!.Status = 'Closed';
538
+ const result = await workflow.RedeemLink(token, link);
539
+ expect(result).toMatchObject({ Success: true, VerificationState: 'verified' });
540
+ expect(result.MaxSessionDeadlineIso).toBeUndefined();
541
+ expect(ports.Config()['maxSessionDeadlineIso']).toBe(new Date(T0 + 300_000).toISOString());
542
+ expect(ports.Published).toHaveLength(0);
543
+ expect(ports.Audits).toHaveLength(1);
544
+ });
545
+
546
+ it('reports persist_failed when a valid link cannot be recorded, leaving it redeemable', async () => {
547
+ const token = await requestedToken();
548
+ ports.PersistFails = true;
549
+ expect(await workflow.RedeemLink(token, link)).toMatchObject({ Success: false, ErrorCode: 'persist_failed' });
550
+ ports.PersistFails = false;
551
+ expect((await workflow.RedeemLink(token, link)).Success).toBe(true);
552
+ });
553
+
554
+ it('is unavailable when the feature is off', async () => {
555
+ const token = await requestedToken();
556
+ ports.Settings.Enabled = false;
557
+ expect(await workflow.RedeemLink(token, link)).toMatchObject({ ErrorCode: 'verification_unavailable' });
558
+ });
559
+ });
560
+
561
+ describe('deadline extension', () => {
562
+ const deadlineAt = (offsetMs: number) => new Date(T0 + offsetMs).toISOString();
563
+
564
+ async function verifyViaCode(): Promise<string | undefined> {
565
+ await workflow.RequestVerification(REQUEST, principal());
566
+ const result = await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: secretsFrom(ports).code }, principal());
567
+ return result.MaxSessionDeadlineIso;
568
+ }
569
+
570
+ it('extends maxSessionDeadlineIso to start + verifiedMaxSeconds, writes it, and tells the session', async () => {
571
+ ports.Settings.PolicyDefaults = { verifiedMaxSeconds: 1800 };
572
+ ports.AddSession({ ConfigRaw: JSON.stringify({ maxSessionDeadlineIso: deadlineAt(300_000) }) });
573
+ ports.Now_ = T0 + 120_000;
574
+ expect(await verifyViaCode()).toBe(deadlineAt(1_800_000));
575
+ expect(ports.Config()['maxSessionDeadlineIso']).toBe(deadlineAt(1_800_000));
576
+ expect(ports.Published[0].payload.MaxSessionDeadlineIso).toBe(deadlineAt(1_800_000));
577
+ expect(ports.Audits[0].MaxSessionDeadlineIso).toBe(deadlineAt(1_800_000));
578
+ });
579
+
580
+ it('takes verifiedMaxSeconds from the per-session snapshot', async () => {
581
+ ports.AddSession({ ConfigRaw: JSON.stringify({ maxSessionDeadlineIso: deadlineAt(300_000), identityVerification: { SendCount: 0, Policy: { verifiedMaxSeconds: 900 } } }) });
582
+ expect(await verifyViaCode()).toBe(deadlineAt(900_000));
583
+ });
584
+
585
+ it('never shortens a deadline', async () => {
586
+ ports.Settings.PolicyDefaults = { verifiedMaxSeconds: 60 };
587
+ ports.AddSession({ ConfigRaw: JSON.stringify({ maxSessionDeadlineIso: deadlineAt(300_000) }) });
588
+ expect(await verifyViaCode()).toBeUndefined();
589
+ expect(ports.Config()['maxSessionDeadlineIso']).toBe(deadlineAt(300_000));
590
+ });
591
+
592
+ it('leaves an uncapped session uncapped', async () => {
593
+ ports.Settings.PolicyDefaults = { verifiedMaxSeconds: 1800 };
594
+ expect(await verifyViaCode()).toBeUndefined();
595
+ expect(ports.Config()['maxSessionDeadlineIso']).toBeUndefined();
596
+ });
597
+
598
+ it('leaves the deadline alone when no verified cap is configured', async () => {
599
+ ports.AddSession({ ConfigRaw: JSON.stringify({ maxSessionDeadlineIso: deadlineAt(300_000) }) });
600
+ expect(await verifyViaCode()).toBeUndefined();
601
+ expect(ports.Config()['maxSessionDeadlineIso']).toBe(deadlineAt(300_000));
602
+ });
603
+ });
604
+
605
+ describe('GetStatus', () => {
606
+ it('reports unverified with the sends left', async () => {
607
+ expect(await workflow.GetStatus({ AgentSessionID: SESSION_ID }, principal())).toMatchObject({ Success: true, VerificationState: 'unverified', SendsRemaining: 3 });
608
+ });
609
+
610
+ it('reports a live pending verification with its expiry and attempts left, and not an expired one', async () => {
611
+ await workflow.RequestVerification(REQUEST, principal());
612
+ const live = await workflow.GetStatus({ AgentSessionID: SESSION_ID }, principal());
613
+ expect(live).toMatchObject({ VerificationState: 'pending', SendsRemaining: 2, AttemptsRemaining: 5 });
614
+ expect(live.ExpiresAt).toBeDefined();
615
+ ports.Now_ = T0 + 31 * 60_000;
616
+ expect((await workflow.GetStatus({ AgentSessionID: SESSION_ID }, principal())).VerificationState).toBe('unverified');
617
+ });
618
+
619
+ it('reports verified (the durable backstop for a missed event) and works on a closed session', async () => {
620
+ await workflow.RequestVerification(REQUEST, principal());
621
+ await workflow.RedeemLink(secretsFrom(ports).token, { Kind: 'link' });
622
+ ports.Sessions.get(SESSION_ID.toLowerCase())!.Status = 'Closed';
623
+ expect(await workflow.GetStatus({ AgentSessionID: SESSION_ID }, principal())).toMatchObject({ Success: true, VerificationState: 'verified', VerifiedEmail: 'pat@acme.com' });
624
+ });
625
+
626
+ it('never leaks pending hashes and refuses non-owners like a missing session', async () => {
627
+ await workflow.RequestVerification(REQUEST, principal());
628
+ const status = await workflow.GetStatus({ AgentSessionID: SESSION_ID }, principal());
629
+ expect(JSON.stringify(status)).not.toMatch(/Hash|Salt|mj_rv_/);
630
+ expect(await workflow.GetStatus({ AgentSessionID: SESSION_ID }, principal({ ID: STRANGER }))).toMatchObject({ ErrorCode: 'session_not_found' });
631
+ });
632
+ });
633
+ });