@open-mercato/core 0.7.0 → 0.7.1-develop.7102.1.b41f7e3e51

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 (847) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/api.js +13 -1
  3. package/dist/helpers/integration/api.js.map +2 -2
  4. package/dist/helpers/integration/communicationChannelsFixtures.js +19 -0
  5. package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
  6. package/dist/modules/api_keys/api/keys/route.js +1 -1
  7. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  8. package/dist/modules/api_keys/backend/api-keys/page.js +3 -1
  9. package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
  10. package/dist/modules/api_keys/di.js +9 -0
  11. package/dist/modules/api_keys/di.js.map +7 -0
  12. package/dist/modules/api_keys/services/principalService.js +31 -0
  13. package/dist/modules/api_keys/services/principalService.js.map +7 -0
  14. package/dist/modules/attachments/api/route.js +2 -2
  15. package/dist/modules/attachments/api/route.js.map +2 -2
  16. package/dist/modules/attachments/components/AttachmentLibrary.js +2 -0
  17. package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
  18. package/dist/modules/attachments/di.js +11 -1
  19. package/dist/modules/attachments/di.js.map +2 -2
  20. package/dist/modules/attachments/index.js.map +1 -1
  21. package/dist/modules/attachments/lib/attachment-service.js +313 -0
  22. package/dist/modules/attachments/lib/attachment-service.js.map +7 -0
  23. package/dist/modules/attachments/lib/scoped-upload-service.js +15 -2
  24. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  25. package/dist/modules/attachments/lib/upload-limits.js +6 -2
  26. package/dist/modules/attachments/lib/upload-limits.js.map +2 -2
  27. package/dist/modules/audit_logs/backend/audit-logs/page.js +6 -0
  28. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  29. package/dist/modules/audit_logs/lib/display-helpers.js +19 -11
  30. package/dist/modules/audit_logs/lib/display-helpers.js.map +2 -2
  31. package/dist/modules/audit_logs/services/actionLogService.js +57 -12
  32. package/dist/modules/audit_logs/services/actionLogService.js.map +3 -3
  33. package/dist/modules/auth/acl.js +30 -5
  34. package/dist/modules/auth/acl.js.map +2 -2
  35. package/dist/modules/auth/api/feature-check.js +8 -2
  36. package/dist/modules/auth/api/feature-check.js.map +2 -2
  37. package/dist/modules/auth/api/login.js +4 -1
  38. package/dist/modules/auth/api/login.js.map +2 -2
  39. package/dist/modules/auth/api/reset/validate.js +55 -0
  40. package/dist/modules/auth/api/reset/validate.js.map +7 -0
  41. package/dist/modules/auth/api/roles/acl/route.js +28 -33
  42. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  43. package/dist/modules/auth/api/session/refresh.js +25 -1
  44. package/dist/modules/auth/api/session/refresh.js.map +2 -2
  45. package/dist/modules/auth/api/users/acl/route.js +96 -51
  46. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  47. package/dist/modules/auth/api/users/route.js +13 -5
  48. package/dist/modules/auth/api/users/route.js.map +2 -2
  49. package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js +5 -1
  50. package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js.map +2 -2
  51. package/dist/modules/auth/backend/roles/page.js +3 -1
  52. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  53. package/dist/modules/auth/backend/users/[id]/edit/page.meta.js +5 -1
  54. package/dist/modules/auth/backend/users/[id]/edit/page.meta.js.map +2 -2
  55. package/dist/modules/auth/backend/users/page.js +2 -1
  56. package/dist/modules/auth/backend/users/page.js.map +2 -2
  57. package/dist/modules/auth/commands/acl.js +254 -0
  58. package/dist/modules/auth/commands/acl.js.map +7 -0
  59. package/dist/modules/auth/components/AclEditor.js +39 -30
  60. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  61. package/dist/modules/auth/data/entities.js.map +2 -2
  62. package/dist/modules/auth/data/validators.js +5 -1
  63. package/dist/modules/auth/data/validators.js.map +2 -2
  64. package/dist/modules/auth/di.js +35 -9
  65. package/dist/modules/auth/di.js.map +2 -2
  66. package/dist/modules/auth/frontend/reset/[token]/page.js +46 -0
  67. package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
  68. package/dist/modules/auth/lib/grantChecks.js.map +2 -2
  69. package/dist/modules/auth/lib/userIdFilter.js +16 -0
  70. package/dist/modules/auth/lib/userIdFilter.js.map +7 -0
  71. package/dist/modules/auth/services/authService.js +16 -2
  72. package/dist/modules/auth/services/authService.js.map +2 -2
  73. package/dist/modules/auth/services/principalService.js +200 -0
  74. package/dist/modules/auth/services/principalService.js.map +7 -0
  75. package/dist/modules/auth/services/rbacService.js +67 -24
  76. package/dist/modules/auth/services/rbacService.js.map +3 -3
  77. package/dist/modules/auth/services/roleOrganizationScope.js +41 -0
  78. package/dist/modules/auth/services/roleOrganizationScope.js.map +7 -0
  79. package/dist/modules/business_rules/backend/logs/page.js +3 -1
  80. package/dist/modules/business_rules/backend/logs/page.js.map +2 -2
  81. package/dist/modules/business_rules/backend/rules/page.js +3 -1
  82. package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
  83. package/dist/modules/business_rules/backend/sets/page.js +3 -1
  84. package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
  85. package/dist/modules/business_rules/lib/expression-evaluator.js +9 -9
  86. package/dist/modules/business_rules/lib/expression-evaluator.js.map +2 -2
  87. package/dist/modules/business_rules/lib/rule-evaluator.js +10 -9
  88. package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
  89. package/dist/modules/catalog/ai-agents.js +1 -1
  90. package/dist/modules/catalog/ai-agents.js.map +1 -1
  91. package/dist/modules/catalog/ai-tools/merchandising-pack.js +2 -2
  92. package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +2 -2
  93. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +5 -2
  94. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
  95. package/dist/modules/catalog/components/categories/CategoriesDataTable.js +2 -0
  96. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
  97. package/dist/modules/catalog/components/products/ProductsDataTable.js +7 -1
  98. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  99. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +2 -0
  100. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +2 -2
  101. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +9 -1
  102. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
  103. package/dist/modules/communication_channels/api/post/test-seed/route.js +95 -10
  104. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  105. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +3 -0
  106. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
  107. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +14 -1
  108. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
  109. package/dist/modules/communication_channels/di.js +7 -1
  110. package/dist/modules/communication_channels/di.js.map +2 -2
  111. package/dist/modules/communication_channels/lib/email-capabilities.js +4 -1
  112. package/dist/modules/communication_channels/lib/email-capabilities.js.map +2 -2
  113. package/dist/modules/communication_channels/lib/outbound-recipient.js +33 -0
  114. package/dist/modules/communication_channels/lib/outbound-recipient.js.map +7 -0
  115. package/dist/modules/communication_channels/lib/resolve-channel-type.js +51 -0
  116. package/dist/modules/communication_channels/lib/resolve-channel-type.js.map +7 -0
  117. package/dist/modules/communication_channels/lib/test-seed.js +41 -2
  118. package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
  119. package/dist/modules/configs/lib/system-status.js +9 -0
  120. package/dist/modules/configs/lib/system-status.js.map +2 -2
  121. package/dist/modules/currencies/backend/currencies/page.js +3 -1
  122. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  123. package/dist/modules/currencies/backend/exchange-rates/page.js +3 -1
  124. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  125. package/dist/modules/currencies/services/rateFetchingService.js +6 -1
  126. package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
  127. package/dist/modules/customer_accounts/api/admin/domain-mappings.js +9 -0
  128. package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +2 -2
  129. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +3 -1
  130. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  131. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +3 -1
  132. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
  133. package/dist/modules/customer_accounts/services/domainMappingService.js +10 -0
  134. package/dist/modules/customer_accounts/services/domainMappingService.js.map +2 -2
  135. package/dist/modules/customers/ai-tools/companies-pack.js +4 -1
  136. package/dist/modules/customers/ai-tools/companies-pack.js.map +2 -2
  137. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +13 -2
  138. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +2 -2
  139. package/dist/modules/customers/ai-tools/deals-pack.js +52 -6
  140. package/dist/modules/customers/ai-tools/deals-pack.js.map +2 -2
  141. package/dist/modules/customers/ai-tools/people-pack.js +4 -1
  142. package/dist/modules/customers/ai-tools/people-pack.js.map +2 -2
  143. package/dist/modules/customers/api/activities/route.js +48 -6
  144. package/dist/modules/customers/api/activities/route.js.map +2 -2
  145. package/dist/modules/customers/api/companies/[id]/people/route.js +20 -64
  146. package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
  147. package/dist/modules/customers/api/companies/[id]/route.js +17 -94
  148. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  149. package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js +8 -5
  150. package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js.map +2 -2
  151. package/dist/modules/customers/api/deals/[id]/companies/route.js +1 -1
  152. package/dist/modules/customers/api/deals/[id]/companies/route.js.map +2 -2
  153. package/dist/modules/customers/api/deals/[id]/people/route.js +1 -1
  154. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  155. package/dist/modules/customers/api/deals/[id]/route.js +8 -25
  156. package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
  157. package/dist/modules/customers/api/deals/[id]/stats/route.js +3 -22
  158. package/dist/modules/customers/api/deals/[id]/stats/route.js.map +2 -2
  159. package/dist/modules/customers/api/deals/aggregate/route.js +11 -4
  160. package/dist/modules/customers/api/deals/aggregate/route.js.map +2 -2
  161. package/dist/modules/customers/api/deals/map/route.js +2 -1
  162. package/dist/modules/customers/api/deals/map/route.js.map +2 -2
  163. package/dist/modules/customers/api/deals/route.js +38 -1
  164. package/dist/modules/customers/api/deals/route.js.map +2 -2
  165. package/dist/modules/customers/api/entity-roles-factory.js +4 -4
  166. package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
  167. package/dist/modules/customers/api/interactions/route.js +3 -1
  168. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  169. package/dist/modules/customers/api/interactions/tasks/route.js +27 -4
  170. package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
  171. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +4 -3
  172. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
  173. package/dist/modules/customers/api/people/[id]/companies/context.js +1 -1
  174. package/dist/modules/customers/api/people/[id]/companies/context.js.map +2 -2
  175. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +1 -1
  176. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
  177. package/dist/modules/customers/api/people/[id]/route.js +11 -10
  178. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  179. package/dist/modules/customers/api/people/check-phone/route.js +51 -9
  180. package/dist/modules/customers/api/people/check-phone/route.js.map +2 -2
  181. package/dist/modules/customers/api/todos/route.js +30 -5
  182. package/dist/modules/customers/api/todos/route.js.map +2 -2
  183. package/dist/modules/customers/backend/customers/companies/page.js +4 -2
  184. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  185. package/dist/modules/customers/backend/customers/deals/[id]/page.js +6 -1
  186. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  187. package/dist/modules/customers/backend/customers/deals/page.js +5 -2
  188. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  189. package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js +4 -13
  190. package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js.map +2 -2
  191. package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js +85 -14
  192. package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js.map +2 -2
  193. package/dist/modules/customers/backend/customers/deals/pipeline/components/toneClasses.js +18 -0
  194. package/dist/modules/customers/backend/customers/deals/pipeline/components/toneClasses.js.map +7 -0
  195. package/dist/modules/customers/backend/customers/people/page.js +4 -2
  196. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  197. package/dist/modules/customers/backend/hooks/useEmailDuplicateCheck.js +9 -3
  198. package/dist/modules/customers/backend/hooks/useEmailDuplicateCheck.js.map +2 -2
  199. package/dist/modules/customers/commands/deals.js +21 -34
  200. package/dist/modules/customers/commands/deals.js.map +2 -2
  201. package/dist/modules/customers/commands/interactions.js.map +1 -1
  202. package/dist/modules/customers/commands/personCompanyLinks.js +131 -2
  203. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  204. package/dist/modules/customers/components/CustomerTodosTable.js +1 -0
  205. package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
  206. package/dist/modules/customers/components/DealsKpiStrip.js +7 -6
  207. package/dist/modules/customers/components/DealsKpiStrip.js.map +2 -2
  208. package/dist/modules/customers/components/calendar/AgendaList.js +2 -2
  209. package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
  210. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +1 -1
  211. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
  212. package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -1
  213. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
  214. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +1 -1
  215. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  216. package/dist/modules/customers/components/calendar/EventBlock.js +1 -1
  217. package/dist/modules/customers/components/calendar/EventBlock.js.map +2 -2
  218. package/dist/modules/customers/components/calendar/EventPeekPopover.js +3 -2
  219. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
  220. package/dist/modules/customers/components/calendar/editor/PeopleField.js +40 -5
  221. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  222. package/dist/modules/customers/components/calendar/editor/hooks.js +2 -1
  223. package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
  224. package/dist/modules/customers/components/calendar/types.js +1 -1
  225. package/dist/modules/customers/components/calendar/types.js.map +2 -2
  226. package/dist/modules/customers/components/detail/CompanyKpiBar.js +5 -4
  227. package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
  228. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +3 -1
  229. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  230. package/dist/modules/customers/components/detail/DealForm.js +19 -7
  231. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  232. package/dist/modules/customers/components/detail/DealsSection.js +6 -5
  233. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  234. package/dist/modules/customers/components/detail/EntityTagsDialog.js +35 -11
  235. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
  236. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +3 -1
  237. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
  238. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +19 -13
  239. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  240. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -4
  241. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  242. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +3 -3
  243. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  244. package/dist/modules/customers/components/detail/utils.js +5 -5
  245. package/dist/modules/customers/components/detail/utils.js.map +2 -2
  246. package/dist/modules/customers/data/entities.js.map +1 -1
  247. package/dist/modules/customers/data/validators.js +32 -3
  248. package/dist/modules/customers/data/validators.js.map +2 -2
  249. package/dist/modules/customers/events.js +5 -0
  250. package/dist/modules/customers/events.js.map +2 -2
  251. package/dist/modules/customers/lib/calendar/conflicts.js +8 -2
  252. package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
  253. package/dist/modules/customers/lib/calendar/editorPayload.js +3 -2
  254. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  255. package/dist/modules/customers/lib/calendar/mapItem.js +8 -3
  256. package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
  257. package/dist/modules/customers/lib/calendar/participantIdentity.js +9 -0
  258. package/dist/modules/customers/lib/calendar/participantIdentity.js.map +7 -0
  259. package/dist/modules/customers/lib/closureStage.js +45 -0
  260. package/dist/modules/customers/lib/closureStage.js.map +7 -0
  261. package/dist/modules/customers/lib/dealStatus.js +30 -0
  262. package/dist/modules/customers/lib/dealStatus.js.map +2 -2
  263. package/dist/modules/customers/lib/detailReadAccess.js +12 -0
  264. package/dist/modules/customers/lib/detailReadAccess.js.map +7 -0
  265. package/dist/modules/customers/lib/findPeopleByAddresses.js +1 -0
  266. package/dist/modules/customers/lib/findPeopleByAddresses.js.map +2 -2
  267. package/dist/modules/customers/lib/personCompanies.js +100 -2
  268. package/dist/modules/customers/lib/personCompanies.js.map +2 -2
  269. package/dist/modules/customers/lib/todoCompatibility.js +12 -0
  270. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  271. package/dist/modules/dashboards/lib/widgetScope.js +6 -1
  272. package/dist/modules/dashboards/lib/widgetScope.js.map +2 -2
  273. package/dist/modules/data_sync/backend/data-sync/page.js +3 -1
  274. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  275. package/dist/modules/data_sync/lib/abandoned-run.js +19 -0
  276. package/dist/modules/data_sync/lib/abandoned-run.js.map +7 -0
  277. package/dist/modules/data_sync/lib/sync-engine.js +72 -6
  278. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  279. package/dist/modules/data_sync/workers/sync-export.js +5 -1
  280. package/dist/modules/data_sync/workers/sync-export.js.map +2 -2
  281. package/dist/modules/data_sync/workers/sync-import.js +5 -1
  282. package/dist/modules/data_sync/workers/sync-import.js.map +2 -2
  283. package/dist/modules/devices/acl.js +9 -1
  284. package/dist/modules/devices/acl.js.map +2 -2
  285. package/dist/modules/devices/api/admin/devices/[id]/route.js +5 -32
  286. package/dist/modules/devices/api/admin/devices/[id]/route.js.map +2 -2
  287. package/dist/modules/devices/api/admin/devices/route.js +14 -1
  288. package/dist/modules/devices/api/admin/devices/route.js.map +2 -2
  289. package/dist/modules/devices/api/deviceList.js +99 -12
  290. package/dist/modules/devices/api/deviceList.js.map +2 -2
  291. package/dist/modules/devices/api/deviceSerialization.js +46 -0
  292. package/dist/modules/devices/api/deviceSerialization.js.map +7 -0
  293. package/dist/modules/devices/api/openapi.js +3 -1
  294. package/dist/modules/devices/api/openapi.js.map +2 -2
  295. package/dist/modules/devices/api/route.js +9 -1
  296. package/dist/modules/devices/api/route.js.map +2 -2
  297. package/dist/modules/devices/backend/devices/[id]/page.js +10 -26
  298. package/dist/modules/devices/backend/devices/[id]/page.js.map +2 -2
  299. package/dist/modules/devices/backend/devices/create/page.js +17 -1
  300. package/dist/modules/devices/backend/devices/create/page.js.map +2 -2
  301. package/dist/modules/devices/backend/devices/page.js +28 -34
  302. package/dist/modules/devices/backend/devices/page.js.map +2 -2
  303. package/dist/modules/devices/backend/devices/useDeviceUserLabels.js +34 -0
  304. package/dist/modules/devices/backend/devices/useDeviceUserLabels.js.map +7 -0
  305. package/dist/modules/devices/backend/devices/userOptions.js +52 -0
  306. package/dist/modules/devices/backend/devices/userOptions.js.map +7 -0
  307. package/dist/modules/devices/setup.js +6 -2
  308. package/dist/modules/devices/setup.js.map +2 -2
  309. package/dist/modules/directory/backend/directory/organizations/page.js +2 -1
  310. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  311. package/dist/modules/directory/backend/directory/tenants/page.js +2 -1
  312. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  313. package/dist/modules/directory/di.js +9 -0
  314. package/dist/modules/directory/di.js.map +2 -2
  315. package/dist/modules/directory/services/organizationHierarchyService.js +29 -0
  316. package/dist/modules/directory/services/organizationHierarchyService.js.map +7 -0
  317. package/dist/modules/directory/services/organizationScopeService.js +56 -0
  318. package/dist/modules/directory/services/organizationScopeService.js.map +7 -0
  319. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  320. package/dist/modules/entities/api/records.js +4 -2
  321. package/dist/modules/entities/api/records.js.map +2 -2
  322. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +3 -1
  323. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  324. package/dist/modules/eudr/api/plots/route.js +5 -1
  325. package/dist/modules/eudr/api/plots/route.js.map +2 -2
  326. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js +3 -0
  327. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js.map +2 -2
  328. package/dist/modules/eudr/backend/eudr/plots/page.js +3 -0
  329. package/dist/modules/eudr/backend/eudr/plots/page.js.map +2 -2
  330. package/dist/modules/eudr/backend/eudr/product-mappings/page.js +3 -0
  331. package/dist/modules/eudr/backend/eudr/product-mappings/page.js.map +2 -2
  332. package/dist/modules/eudr/backend/eudr/risk-assessments/page.js +3 -0
  333. package/dist/modules/eudr/backend/eudr/risk-assessments/page.js.map +2 -2
  334. package/dist/modules/eudr/backend/eudr/statements/page.js +3 -0
  335. package/dist/modules/eudr/backend/eudr/statements/page.js.map +2 -2
  336. package/dist/modules/eudr/commands/statements.js +0 -3
  337. package/dist/modules/eudr/commands/statements.js.map +2 -2
  338. package/dist/modules/eudr/data/validators.js +4 -1
  339. package/dist/modules/eudr/data/validators.js.map +2 -2
  340. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +1 -0
  341. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  342. package/dist/modules/feature_toggles/components/OverridesTable.js +1 -0
  343. package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
  344. package/dist/modules/feature_toggles/defaults.json +0 -8
  345. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +3 -0
  346. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
  347. package/dist/modules/inbox_ops/backend/inbox-ops/page.js +3 -0
  348. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
  349. package/dist/modules/inbox_ops/lib/htmlToPlainText.js +1 -12
  350. package/dist/modules/inbox_ops/lib/htmlToPlainText.js.map +2 -2
  351. package/dist/modules/integrations/api/[id]/credentials/route.js +6 -1
  352. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  353. package/dist/modules/integrations/backend/integrations/[id]/page.js +38 -29
  354. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  355. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +36 -9
  356. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  357. package/dist/modules/integrations/backend/integrations/credential-secret-fields.js +55 -0
  358. package/dist/modules/integrations/backend/integrations/credential-secret-fields.js.map +7 -0
  359. package/dist/modules/integrations/data/validators.js +11 -3
  360. package/dist/modules/integrations/data/validators.js.map +2 -2
  361. package/dist/modules/integrations/lib/credentials-masking.js +6 -2
  362. package/dist/modules/integrations/lib/credentials-masking.js.map +2 -2
  363. package/dist/modules/messages/api/[id]/route.js +29 -1
  364. package/dist/modules/messages/api/[id]/route.js.map +2 -2
  365. package/dist/modules/messages/api/openapi.js +17 -2
  366. package/dist/modules/messages/api/openapi.js.map +2 -2
  367. package/dist/modules/messages/api/route.js +17 -2
  368. package/dist/modules/messages/api/route.js.map +2 -2
  369. package/dist/modules/messages/components/MessagesInboxPageClient.js +7 -14
  370. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  371. package/dist/modules/messages/components/inboxFilters.js +15 -0
  372. package/dist/modules/messages/components/inboxFilters.js.map +2 -2
  373. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js +11 -1
  374. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map +2 -2
  375. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js +3 -1
  376. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js.map +2 -2
  377. package/dist/modules/messages/components/message-detail/panels/thread-panel.js +2 -1
  378. package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +2 -2
  379. package/dist/modules/messages/components/messageListLabels.js +14 -2
  380. package/dist/modules/messages/components/messageListLabels.js.map +2 -2
  381. package/dist/modules/messages/data/validators.js +18 -4
  382. package/dist/modules/messages/data/validators.js.map +2 -2
  383. package/dist/modules/messages/lib/channel-sender-identity.js +26 -0
  384. package/dist/modules/messages/lib/channel-sender-identity.js.map +7 -0
  385. package/dist/modules/messages/lib/composeSourceChannelType.js +41 -0
  386. package/dist/modules/messages/lib/composeSourceChannelType.js.map +7 -0
  387. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +4 -6
  388. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  389. package/dist/modules/payment_gateways/backend/payment-gateways/page.js +3 -1
  390. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
  391. package/dist/modules/payment_gateways/lib/webhook-processor.js +7 -0
  392. package/dist/modules/payment_gateways/lib/webhook-processor.js.map +2 -2
  393. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +3 -0
  394. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  395. package/dist/modules/push_notifications/backend/push_notifications/page.js +3 -1
  396. package/dist/modules/push_notifications/backend/push_notifications/page.js.map +2 -2
  397. package/dist/modules/push_notifications/backend/push_notifications/send/page.js +1 -1
  398. package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +2 -2
  399. package/dist/modules/query_index/lib/engine.js +150 -51
  400. package/dist/modules/query_index/lib/engine.js.map +2 -2
  401. package/dist/modules/query_index/lib/search-entity-policy.js +14 -0
  402. package/dist/modules/query_index/lib/search-entity-policy.js.map +7 -0
  403. package/dist/modules/query_index/lib/search-tokens.js +79 -3
  404. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  405. package/dist/modules/resources/backend/resources/resource-types/page.js +3 -1
  406. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  407. package/dist/modules/resources/backend/resources/resources/page.js +3 -1
  408. package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
  409. package/dist/modules/sales/backend/sales/channels/offers/page.js +3 -0
  410. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  411. package/dist/modules/sales/backend/sales/channels/page.js +3 -0
  412. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  413. package/dist/modules/sales/backend/sales/documents/[id]/page.js +10 -8
  414. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  415. package/dist/modules/sales/commands/documents.js +79 -1
  416. package/dist/modules/sales/commands/documents.js.map +2 -2
  417. package/dist/modules/sales/components/PriceWithCurrency.js +7 -5
  418. package/dist/modules/sales/components/PriceWithCurrency.js.map +2 -2
  419. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +3 -0
  420. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  421. package/dist/modules/sales/components/documents/AdjustmentsSection.js +8 -6
  422. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  423. package/dist/modules/sales/components/documents/ItemsSection.js +51 -22
  424. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  425. package/dist/modules/sales/components/documents/LineItemDialog.js +83 -18
  426. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  427. package/dist/modules/sales/components/documents/PaymentsSection.js +23 -9
  428. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  429. package/dist/modules/sales/components/documents/ReturnsSection.js +8 -6
  430. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  431. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +68 -14
  432. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  433. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +6 -5
  434. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
  435. package/dist/modules/sales/components/documents/ShipmentDialog.js +5 -4
  436. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  437. package/dist/modules/sales/components/documents/ShipmentsSection.js +8 -6
  438. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  439. package/dist/modules/sales/components/documents/lineItemShipmentLock.js +54 -0
  440. package/dist/modules/sales/components/documents/lineItemShipmentLock.js.map +7 -0
  441. package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
  442. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  443. package/dist/modules/sales/data/entities.js +8 -2
  444. package/dist/modules/sales/data/entities.js.map +2 -2
  445. package/dist/modules/sales/extension-points.js +1 -0
  446. package/dist/modules/sales/extension-points.js.map +2 -2
  447. package/dist/modules/sales/lib/salesChannelsToggleSeed.js +33 -0
  448. package/dist/modules/sales/lib/salesChannelsToggleSeed.js.map +7 -0
  449. package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js +32 -0
  450. package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js.map +7 -0
  451. package/dist/modules/sales/setup.js +2 -0
  452. package/dist/modules/sales/setup.js.map +2 -2
  453. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js +10 -2
  454. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js.map +2 -2
  455. package/dist/modules/sales/widgets/injection-table.js +4 -7
  456. package/dist/modules/sales/widgets/injection-table.js.map +2 -2
  457. package/dist/modules/staff/backend/staff/leave-requests/page.js +3 -0
  458. package/dist/modules/staff/backend/staff/leave-requests/page.js.map +2 -2
  459. package/dist/modules/staff/backend/staff/my-leave-requests/page.js +3 -0
  460. package/dist/modules/staff/backend/staff/my-leave-requests/page.js.map +2 -2
  461. package/dist/modules/staff/backend/staff/team-members/page.js +3 -0
  462. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  463. package/dist/modules/staff/backend/staff/team-roles/page.js +3 -0
  464. package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
  465. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +3 -0
  466. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  467. package/dist/modules/staff/backend/staff/teams/page.js +3 -0
  468. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  469. package/dist/modules/staff/backend/staff/timesheets/page.js +29 -18
  470. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  471. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +3 -0
  472. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  473. package/dist/modules/sync_excel/lib/adapters/customers.js +1 -0
  474. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  475. package/dist/modules/warranty_claims/api/assignees/route.js +3 -3
  476. package/dist/modules/warranty_claims/api/assignees/route.js.map +2 -2
  477. package/dist/modules/warranty_claims/api/portal/attachments/route.js +2 -2
  478. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
  479. package/dist/modules/warranty_claims/api/portal/claims/route.js +12 -9
  480. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  481. package/dist/modules/warranty_claims/backend/page.js +3 -0
  482. package/dist/modules/warranty_claims/backend/page.js.map +2 -2
  483. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js +3 -0
  484. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js.map +2 -2
  485. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js +3 -0
  486. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js.map +2 -2
  487. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js +3 -0
  488. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js.map +2 -2
  489. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -0
  490. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
  491. package/dist/modules/wms/api/warehouseSearch.js +21 -0
  492. package/dist/modules/wms/api/warehouseSearch.js.map +7 -0
  493. package/dist/modules/wms/api/warehouses/route.js +13 -9
  494. package/dist/modules/wms/api/warehouses/route.js.map +2 -2
  495. package/dist/modules/wms/api/zones/route.js +15 -2
  496. package/dist/modules/wms/api/zones/route.js.map +2 -2
  497. package/dist/modules/wms/commands/configuration.js +121 -9
  498. package/dist/modules/wms/commands/configuration.js.map +2 -2
  499. package/dist/modules/wms/commands/inventory-actions.js +3 -0
  500. package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
  501. package/dist/modules/wms/commands/shared.js +9 -1
  502. package/dist/modules/wms/commands/shared.js.map +2 -2
  503. package/dist/modules/wms/components/backend/WarehouseEditDialog.js +158 -0
  504. package/dist/modules/wms/components/backend/WarehouseEditDialog.js.map +7 -0
  505. package/dist/modules/wms/components/backend/WmsConfigurationPage.js +76 -111
  506. package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +3 -3
  507. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js +1 -0
  508. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +2 -2
  509. package/dist/modules/wms/components/backend/WmsLotsListPage.js +1 -0
  510. package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +2 -2
  511. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +27 -20
  512. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +2 -2
  513. package/dist/modules/wms/components/backend/warehouseFormOptions.js +66 -0
  514. package/dist/modules/wms/components/backend/warehouseFormOptions.js.map +7 -0
  515. package/dist/modules/workflows/backend/definitions/page.js +3 -1
  516. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  517. package/dist/modules/workflows/backend/events/page.js +3 -1
  518. package/dist/modules/workflows/backend/events/page.js.map +2 -2
  519. package/dist/modules/workflows/backend/instances/page.js +3 -1
  520. package/dist/modules/workflows/backend/instances/page.js.map +2 -2
  521. package/dist/modules/workflows/backend/tasks/page.js +3 -1
  522. package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
  523. package/dist/modules/workflows/lib/activity-executor.js +4 -0
  524. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  525. package/package.json +9 -10
  526. package/src/helpers/integration/api.ts +33 -1
  527. package/src/helpers/integration/communicationChannelsFixtures.ts +69 -1
  528. package/src/modules/api_keys/api/keys/route.ts +3 -1
  529. package/src/modules/api_keys/backend/api-keys/page.tsx +4 -1
  530. package/src/modules/api_keys/di.ts +7 -0
  531. package/src/modules/api_keys/services/principalService.ts +33 -0
  532. package/src/modules/attachments/AGENTS.md +15 -0
  533. package/src/modules/attachments/api/route.ts +2 -2
  534. package/src/modules/attachments/components/AttachmentLibrary.tsx +3 -0
  535. package/src/modules/attachments/di.ts +16 -0
  536. package/src/modules/attachments/i18n/de.json +5 -0
  537. package/src/modules/attachments/i18n/en.json +5 -0
  538. package/src/modules/attachments/i18n/es.json +5 -0
  539. package/src/modules/attachments/i18n/ko.json +5 -0
  540. package/src/modules/attachments/i18n/pl.json +5 -0
  541. package/src/modules/attachments/index.ts +10 -0
  542. package/src/modules/attachments/lib/attachment-service.ts +468 -0
  543. package/src/modules/attachments/lib/scoped-upload-service.ts +46 -0
  544. package/src/modules/attachments/lib/upload-limits.ts +6 -2
  545. package/src/modules/audit_logs/backend/audit-logs/page.tsx +8 -0
  546. package/src/modules/audit_logs/lib/display-helpers.tsx +42 -32
  547. package/src/modules/audit_logs/services/actionLogService.ts +74 -11
  548. package/src/modules/auth/acl.ts +30 -6
  549. package/src/modules/auth/api/feature-check.ts +10 -3
  550. package/src/modules/auth/api/login.ts +9 -1
  551. package/src/modules/auth/api/reset/validate.ts +57 -0
  552. package/src/modules/auth/api/roles/acl/route.ts +42 -39
  553. package/src/modules/auth/api/session/refresh.ts +32 -1
  554. package/src/modules/auth/api/users/acl/route.ts +177 -53
  555. package/src/modules/auth/api/users/route.ts +18 -5
  556. package/src/modules/auth/backend/roles/[id]/edit/page.meta.ts +5 -2
  557. package/src/modules/auth/backend/roles/page.tsx +4 -2
  558. package/src/modules/auth/backend/users/[id]/edit/page.meta.ts +5 -2
  559. package/src/modules/auth/backend/users/page.tsx +3 -2
  560. package/src/modules/auth/commands/acl.ts +495 -0
  561. package/src/modules/auth/components/AclEditor.tsx +63 -33
  562. package/src/modules/auth/data/entities.ts +4 -1
  563. package/src/modules/auth/data/validators.ts +5 -0
  564. package/src/modules/auth/di.ts +46 -16
  565. package/src/modules/auth/frontend/reset/[token]/page.tsx +74 -0
  566. package/src/modules/auth/i18n/de.json +389 -0
  567. package/src/modules/auth/i18n/en.json +389 -0
  568. package/src/modules/auth/i18n/es.json +389 -0
  569. package/src/modules/auth/i18n/ko.json +389 -0
  570. package/src/modules/auth/i18n/pl.json +391 -2
  571. package/src/modules/auth/lib/grantChecks.ts +6 -0
  572. package/src/modules/auth/lib/userIdFilter.ts +31 -0
  573. package/src/modules/auth/services/authService.ts +18 -2
  574. package/src/modules/auth/services/principalService.ts +266 -0
  575. package/src/modules/auth/services/rbacService.ts +110 -24
  576. package/src/modules/auth/services/roleOrganizationScope.ts +70 -0
  577. package/src/modules/business_rules/backend/logs/page.tsx +4 -1
  578. package/src/modules/business_rules/backend/rules/page.tsx +4 -1
  579. package/src/modules/business_rules/backend/sets/page.tsx +4 -1
  580. package/src/modules/business_rules/lib/expression-evaluator.ts +9 -9
  581. package/src/modules/business_rules/lib/rule-evaluator.ts +10 -9
  582. package/src/modules/catalog/ai-agents.ts +1 -1
  583. package/src/modules/catalog/ai-tools/merchandising-pack.ts +2 -2
  584. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +12 -5
  585. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +3 -0
  586. package/src/modules/catalog/components/products/ProductsDataTable.tsx +7 -0
  587. package/src/modules/catalog/i18n/de.json +1 -0
  588. package/src/modules/catalog/i18n/en.json +1 -0
  589. package/src/modules/catalog/i18n/es.json +1 -0
  590. package/src/modules/catalog/i18n/ko.json +1 -0
  591. package/src/modules/catalog/i18n/pl.json +1 -0
  592. package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +4 -0
  593. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +17 -1
  594. package/src/modules/communication_channels/api/post/test-seed/route.ts +155 -16
  595. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +4 -0
  596. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +38 -1
  597. package/src/modules/communication_channels/di.ts +7 -0
  598. package/src/modules/communication_channels/lib/adapter.ts +14 -0
  599. package/src/modules/communication_channels/lib/email-capabilities.ts +4 -0
  600. package/src/modules/communication_channels/lib/outbound-recipient.ts +71 -0
  601. package/src/modules/communication_channels/lib/resolve-channel-type.ts +99 -0
  602. package/src/modules/communication_channels/lib/test-seed.ts +64 -4
  603. package/src/modules/configs/i18n/de.json +2 -0
  604. package/src/modules/configs/i18n/en.json +2 -0
  605. package/src/modules/configs/i18n/es.json +2 -0
  606. package/src/modules/configs/i18n/ko.json +2 -0
  607. package/src/modules/configs/i18n/pl.json +2 -0
  608. package/src/modules/configs/lib/system-status.ts +9 -0
  609. package/src/modules/currencies/AGENTS.md +3 -0
  610. package/src/modules/currencies/backend/currencies/page.tsx +4 -1
  611. package/src/modules/currencies/backend/exchange-rates/page.tsx +4 -1
  612. package/src/modules/currencies/services/README.md +45 -0
  613. package/src/modules/currencies/services/rateFetchingService.ts +6 -1
  614. package/src/modules/customer_accounts/api/admin/domain-mappings.ts +7 -0
  615. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +4 -1
  616. package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +4 -1
  617. package/src/modules/customer_accounts/services/domainMappingService.ts +10 -0
  618. package/src/modules/customers/ai-tools/companies-pack.ts +4 -1
  619. package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +17 -2
  620. package/src/modules/customers/ai-tools/deals-pack.ts +66 -1
  621. package/src/modules/customers/ai-tools/people-pack.ts +4 -1
  622. package/src/modules/customers/api/activities/route.ts +45 -0
  623. package/src/modules/customers/api/companies/[id]/people/route.ts +24 -73
  624. package/src/modules/customers/api/companies/[id]/route.ts +24 -107
  625. package/src/modules/customers/api/dashboard/widgets/customer-todos/route.ts +16 -8
  626. package/src/modules/customers/api/deals/[id]/companies/route.ts +4 -1
  627. package/src/modules/customers/api/deals/[id]/people/route.ts +4 -1
  628. package/src/modules/customers/api/deals/[id]/route.ts +17 -28
  629. package/src/modules/customers/api/deals/[id]/stats/route.ts +10 -25
  630. package/src/modules/customers/api/deals/aggregate/route.ts +14 -4
  631. package/src/modules/customers/api/deals/map/route.ts +1 -0
  632. package/src/modules/customers/api/deals/route.ts +50 -1
  633. package/src/modules/customers/api/entity-roles-factory.ts +8 -4
  634. package/src/modules/customers/api/interactions/route.ts +8 -2
  635. package/src/modules/customers/api/interactions/tasks/route.ts +24 -1
  636. package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +12 -4
  637. package/src/modules/customers/api/people/[id]/companies/context.ts +4 -1
  638. package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +4 -1
  639. package/src/modules/customers/api/people/[id]/route.ts +16 -11
  640. package/src/modules/customers/api/people/check-phone/route.ts +75 -10
  641. package/src/modules/customers/api/todos/route.ts +27 -2
  642. package/src/modules/customers/backend/customers/companies/page.tsx +5 -2
  643. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +7 -2
  644. package/src/modules/customers/backend/customers/deals/page.tsx +8 -2
  645. package/src/modules/customers/backend/customers/deals/pipeline/components/Lane.tsx +5 -14
  646. package/src/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.tsx +109 -27
  647. package/src/modules/customers/backend/customers/deals/pipeline/components/toneClasses.ts +21 -0
  648. package/src/modules/customers/backend/customers/people/page.tsx +5 -2
  649. package/src/modules/customers/backend/hooks/useEmailDuplicateCheck.ts +12 -4
  650. package/src/modules/customers/commands/deals.ts +34 -59
  651. package/src/modules/customers/commands/interactions.ts +1 -1
  652. package/src/modules/customers/commands/personCompanyLinks.ts +224 -8
  653. package/src/modules/customers/components/CustomerTodosTable.tsx +2 -0
  654. package/src/modules/customers/components/DealsKpiStrip.tsx +7 -7
  655. package/src/modules/customers/components/calendar/AgendaList.tsx +3 -3
  656. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +1 -1
  657. package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -1
  658. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +1 -1
  659. package/src/modules/customers/components/calendar/EventBlock.tsx +2 -2
  660. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +4 -2
  661. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +61 -5
  662. package/src/modules/customers/components/calendar/editor/hooks.ts +7 -1
  663. package/src/modules/customers/components/calendar/types.ts +2 -2
  664. package/src/modules/customers/components/detail/CompanyKpiBar.tsx +5 -4
  665. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +8 -1
  666. package/src/modules/customers/components/detail/DealForm.tsx +22 -2
  667. package/src/modules/customers/components/detail/DealsSection.tsx +11 -5
  668. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +58 -12
  669. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +8 -1
  670. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +23 -14
  671. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +5 -5
  672. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +5 -5
  673. package/src/modules/customers/components/detail/types.ts +1 -1
  674. package/src/modules/customers/components/detail/utils.ts +15 -5
  675. package/src/modules/customers/data/entities.ts +1 -1
  676. package/src/modules/customers/data/validators.ts +50 -9
  677. package/src/modules/customers/events.ts +5 -0
  678. package/src/modules/customers/i18n/de.json +9 -1
  679. package/src/modules/customers/i18n/en.json +9 -1
  680. package/src/modules/customers/i18n/es.json +9 -1
  681. package/src/modules/customers/i18n/ko.json +9 -1
  682. package/src/modules/customers/i18n/pl.json +9 -1
  683. package/src/modules/customers/lib/calendar/conflicts.ts +10 -2
  684. package/src/modules/customers/lib/calendar/editorPayload.ts +4 -3
  685. package/src/modules/customers/lib/calendar/mapItem.ts +8 -3
  686. package/src/modules/customers/lib/calendar/participantIdentity.ts +17 -0
  687. package/src/modules/customers/lib/closureStage.ts +76 -0
  688. package/src/modules/customers/lib/dealStatus.ts +42 -0
  689. package/src/modules/customers/lib/detailReadAccess.ts +35 -0
  690. package/src/modules/customers/lib/findPeopleByAddresses.ts +8 -6
  691. package/src/modules/customers/lib/personCompanies.ts +136 -1
  692. package/src/modules/customers/lib/todoCompatibility.ts +19 -1
  693. package/src/modules/dashboards/i18n/es.json +4 -4
  694. package/src/modules/dashboards/i18n/pl.json +7 -7
  695. package/src/modules/dashboards/lib/widgetScope.ts +6 -0
  696. package/src/modules/data_sync/AGENTS.md +1 -1
  697. package/src/modules/data_sync/backend/data-sync/page.tsx +4 -1
  698. package/src/modules/data_sync/lib/abandoned-run.ts +56 -0
  699. package/src/modules/data_sync/lib/adapter.ts +36 -0
  700. package/src/modules/data_sync/lib/sync-engine.ts +113 -5
  701. package/src/modules/data_sync/workers/sync-export.ts +4 -0
  702. package/src/modules/data_sync/workers/sync-import.ts +4 -0
  703. package/src/modules/devices/AGENTS.md +20 -1
  704. package/src/modules/devices/acl.ts +9 -1
  705. package/src/modules/devices/api/admin/devices/[id]/route.ts +5 -34
  706. package/src/modules/devices/api/admin/devices/route.ts +14 -1
  707. package/src/modules/devices/api/deviceList.ts +135 -11
  708. package/src/modules/devices/api/deviceSerialization.ts +50 -0
  709. package/src/modules/devices/api/openapi.ts +9 -1
  710. package/src/modules/devices/api/route.ts +9 -1
  711. package/src/modules/devices/backend/devices/[id]/page.tsx +18 -32
  712. package/src/modules/devices/backend/devices/create/page.tsx +17 -1
  713. package/src/modules/devices/backend/devices/page.tsx +44 -45
  714. package/src/modules/devices/backend/devices/useDeviceUserLabels.ts +45 -0
  715. package/src/modules/devices/backend/devices/userOptions.ts +99 -0
  716. package/src/modules/devices/i18n/de.json +3 -2
  717. package/src/modules/devices/i18n/en.json +3 -2
  718. package/src/modules/devices/i18n/es.json +3 -2
  719. package/src/modules/devices/i18n/ko.json +3 -2
  720. package/src/modules/devices/i18n/pl.json +3 -2
  721. package/src/modules/devices/setup.ts +6 -2
  722. package/src/modules/directory/backend/directory/organizations/page.tsx +3 -1
  723. package/src/modules/directory/backend/directory/tenants/page.tsx +3 -1
  724. package/src/modules/directory/di.ts +14 -2
  725. package/src/modules/directory/services/organizationHierarchyService.ts +34 -0
  726. package/src/modules/directory/services/organizationScopeService.ts +85 -0
  727. package/src/modules/directory/utils/organizationScope.ts +4 -15
  728. package/src/modules/entities/api/records.ts +2 -0
  729. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +4 -1
  730. package/src/modules/eudr/api/plots/route.ts +4 -0
  731. package/src/modules/eudr/backend/eudr/evidence-submissions/page.tsx +4 -0
  732. package/src/modules/eudr/backend/eudr/plots/page.tsx +4 -0
  733. package/src/modules/eudr/backend/eudr/product-mappings/page.tsx +4 -0
  734. package/src/modules/eudr/backend/eudr/risk-assessments/page.tsx +4 -0
  735. package/src/modules/eudr/backend/eudr/statements/page.tsx +4 -0
  736. package/src/modules/eudr/commands/statements.ts +0 -3
  737. package/src/modules/eudr/data/validators.ts +16 -1
  738. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +2 -1
  739. package/src/modules/feature_toggles/components/OverridesTable.tsx +2 -1
  740. package/src/modules/feature_toggles/defaults.json +0 -8
  741. package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +4 -0
  742. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +4 -0
  743. package/src/modules/inbox_ops/lib/htmlToPlainText.ts +1 -17
  744. package/src/modules/integrations/api/[id]/credentials/route.ts +6 -4
  745. package/src/modules/integrations/backend/integrations/[id]/page.tsx +61 -31
  746. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +44 -9
  747. package/src/modules/integrations/backend/integrations/credential-secret-fields.ts +84 -0
  748. package/src/modules/integrations/data/validators.ts +12 -2
  749. package/src/modules/integrations/i18n/de.json +1 -0
  750. package/src/modules/integrations/i18n/en.json +1 -0
  751. package/src/modules/integrations/i18n/es.json +1 -0
  752. package/src/modules/integrations/i18n/ko.json +1 -0
  753. package/src/modules/integrations/i18n/pl.json +1 -0
  754. package/src/modules/integrations/lib/credentials-masking.ts +10 -6
  755. package/src/modules/messages/api/[id]/route.ts +55 -1
  756. package/src/modules/messages/api/openapi.ts +20 -0
  757. package/src/modules/messages/api/route.ts +31 -2
  758. package/src/modules/messages/components/MessagesInboxPageClient.tsx +9 -16
  759. package/src/modules/messages/components/inboxFilters.ts +36 -0
  760. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts +14 -1
  761. package/src/modules/messages/components/message-detail/panels/MessageHeader.tsx +3 -1
  762. package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +2 -1
  763. package/src/modules/messages/components/message-detail/types.ts +3 -0
  764. package/src/modules/messages/components/messageListLabels.ts +52 -3
  765. package/src/modules/messages/data/validators.ts +43 -4
  766. package/src/modules/messages/lib/channel-sender-identity.ts +55 -0
  767. package/src/modules/messages/lib/composeSourceChannelType.ts +95 -0
  768. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +4 -6
  769. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +4 -1
  770. package/src/modules/payment_gateways/lib/webhook-processor.ts +11 -0
  771. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +4 -0
  772. package/src/modules/push_notifications/backend/push_notifications/page.tsx +4 -1
  773. package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +4 -4
  774. package/src/modules/query_index/lib/engine.ts +249 -61
  775. package/src/modules/query_index/lib/search-entity-policy.ts +46 -0
  776. package/src/modules/query_index/lib/search-tokens.ts +135 -3
  777. package/src/modules/resources/backend/resources/resource-types/page.tsx +4 -1
  778. package/src/modules/resources/backend/resources/resources/page.tsx +4 -1
  779. package/src/modules/sales/backend/sales/channels/offers/page.tsx +4 -0
  780. package/src/modules/sales/backend/sales/channels/page.tsx +4 -0
  781. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +14 -8
  782. package/src/modules/sales/commands/documents.ts +93 -0
  783. package/src/modules/sales/components/PriceWithCurrency.tsx +17 -5
  784. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +4 -0
  785. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +6 -5
  786. package/src/modules/sales/components/documents/ItemsSection.tsx +56 -18
  787. package/src/modules/sales/components/documents/LineItemDialog.tsx +137 -20
  788. package/src/modules/sales/components/documents/PaymentsSection.tsx +20 -9
  789. package/src/modules/sales/components/documents/ReturnsSection.tsx +6 -5
  790. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +106 -14
  791. package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +6 -5
  792. package/src/modules/sales/components/documents/ShipmentDialog.tsx +5 -4
  793. package/src/modules/sales/components/documents/ShipmentsSection.tsx +6 -5
  794. package/src/modules/sales/components/documents/lineItemShipmentLock.ts +74 -0
  795. package/src/modules/sales/components/documents/lineItemUtils.ts +7 -2
  796. package/src/modules/sales/data/entities.ts +6 -0
  797. package/src/modules/sales/extension-points.ts +1 -0
  798. package/src/modules/sales/i18n/de.json +8 -0
  799. package/src/modules/sales/i18n/en.json +8 -0
  800. package/src/modules/sales/i18n/es.json +8 -0
  801. package/src/modules/sales/i18n/ko.json +8 -0
  802. package/src/modules/sales/i18n/pl.json +8 -0
  803. package/src/modules/sales/lib/salesChannelsToggleSeed.ts +42 -0
  804. package/src/modules/sales/migrations/.snapshot-open-mercato.json +60 -0
  805. package/src/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.ts +87 -0
  806. package/src/modules/sales/setup.ts +2 -0
  807. package/src/modules/sales/widgets/injection/payment-gateway-status-column/widget.ts +9 -1
  808. package/src/modules/sales/widgets/injection-table.ts +4 -7
  809. package/src/modules/staff/backend/staff/leave-requests/page.tsx +4 -0
  810. package/src/modules/staff/backend/staff/my-leave-requests/page.tsx +4 -0
  811. package/src/modules/staff/backend/staff/team-members/page.tsx +6 -0
  812. package/src/modules/staff/backend/staff/team-roles/page.tsx +4 -0
  813. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +4 -0
  814. package/src/modules/staff/backend/staff/teams/page.tsx +4 -0
  815. package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1
  816. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +4 -0
  817. package/src/modules/sync_excel/lib/adapters/customers.ts +5 -0
  818. package/src/modules/warranty_claims/api/assignees/route.ts +3 -3
  819. package/src/modules/warranty_claims/api/portal/attachments/route.ts +2 -2
  820. package/src/modules/warranty_claims/api/portal/claims/route.ts +12 -9
  821. package/src/modules/warranty_claims/backend/page.tsx +4 -0
  822. package/src/modules/warranty_claims/backend/warranty_claims/registrations/page.tsx +4 -0
  823. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.tsx +4 -0
  824. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.tsx +4 -0
  825. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +4 -0
  826. package/src/modules/wms/api/warehouseSearch.ts +23 -0
  827. package/src/modules/wms/api/warehouses/route.ts +13 -9
  828. package/src/modules/wms/api/zones/route.ts +16 -0
  829. package/src/modules/wms/commands/configuration.ts +159 -10
  830. package/src/modules/wms/commands/inventory-actions.ts +3 -0
  831. package/src/modules/wms/commands/shared.ts +17 -0
  832. package/src/modules/wms/components/backend/WarehouseEditDialog.tsx +216 -0
  833. package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +95 -141
  834. package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +2 -0
  835. package/src/modules/wms/components/backend/WmsLotsListPage.tsx +2 -0
  836. package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +36 -20
  837. package/src/modules/wms/components/backend/warehouseFormOptions.ts +72 -0
  838. package/src/modules/wms/i18n/de.json +2 -0
  839. package/src/modules/wms/i18n/en.json +2 -0
  840. package/src/modules/wms/i18n/es.json +2 -0
  841. package/src/modules/wms/i18n/ko.json +2 -0
  842. package/src/modules/wms/i18n/pl.json +2 -0
  843. package/src/modules/workflows/backend/definitions/page.tsx +4 -1
  844. package/src/modules/workflows/backend/events/page.tsx +4 -1
  845. package/src/modules/workflows/backend/instances/page.tsx +4 -1
  846. package/src/modules/workflows/backend/tasks/page.tsx +4 -1
  847. package/src/modules/workflows/lib/activity-executor.ts +9 -0
@@ -6,6 +6,7 @@ import { checkRateLimit, getClientIp, RATE_LIMIT_ERROR_FALLBACK } from '@open-me
6
6
  import type { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'
7
7
  import type { EntityManager } from '@mikro-orm/postgresql'
8
8
  import { getWebhookHandler } from '@open-mercato/shared/modules/payment_gateways/types'
9
+ import { markQueueJobOrigin } from '@open-mercato/shared/lib/queue/dispatchOrigin'
9
10
  import type { IntegrationLogService } from '../../../../integrations/lib/log-service'
10
11
  import type { PaymentGatewayService } from '../../../lib/gateway-service'
11
12
  import type { CredentialsService } from '../../../../integrations/lib/credentials-service'
@@ -111,18 +112,15 @@ export async function POST(req: Request, { params }: { params: Promise<{ provide
111
112
 
112
113
  const scope = matchedScope
113
114
 
114
- const jobPayload = {
115
+ const jobPayload = markQueueJobOrigin({
115
116
  providerKey,
116
117
  event,
117
118
  transactionId: transaction.id,
118
119
  scope,
119
- }
120
+ }, 'inbound-webhook')
120
121
 
121
122
  if (process.env.QUEUE_STRATEGY === 'async') {
122
- await queue.enqueue({
123
- name: 'payment-gateway-webhook',
124
- payload: jobPayload,
125
- })
123
+ await queue.enqueue(jobPayload)
126
124
  } else {
127
125
  await processPaymentGatewayWebhookJob(
128
126
  {
@@ -89,6 +89,7 @@ type TransactionsResponse = {
89
89
  page: number
90
90
  pageSize: number
91
91
  totalPages: number
92
+ totalIsCapped?: boolean
92
93
  }
93
94
 
94
95
  const STATUS_STYLES: Record<string, string> = {
@@ -183,6 +184,7 @@ export default function PaymentTransactionsPage() {
183
184
  const [page, setPage] = React.useState(1)
184
185
  const [total, setTotal] = React.useState(0)
185
186
  const [totalPages, setTotalPages] = React.useState(1)
187
+ const [totalIsCapped, setTotalIsCapped] = React.useState(false)
186
188
  const [search, setSearch] = React.useState('')
187
189
  const [filterValues, setFilterValues] = React.useState<FilterValues>({})
188
190
  const [isLoading, setIsLoading] = React.useState(true)
@@ -229,6 +231,7 @@ export default function PaymentTransactionsPage() {
229
231
  setRows(Array.isArray(call.result.items) ? call.result.items : [])
230
232
  setTotal(call.result.total ?? 0)
231
233
  setTotalPages(call.result.totalPages ?? 1)
234
+ setTotalIsCapped(call.result?.totalIsCapped === true)
232
235
  } else {
233
236
  flash(t('payment_gateways.transactions.error.load', 'Failed to load payment transactions'), 'error')
234
237
  setRows([])
@@ -474,7 +477,7 @@ export default function PaymentTransactionsPage() {
474
477
  onSearchChange={(value) => { setSearch(value); setPage(1) }}
475
478
  searchPlaceholder={t('payment_gateways.transactions.searchPlaceholder', 'Search by payment, transaction, session, or gateway id')}
476
479
  perspective={{ tableId: extensionPoints.hosts.transactionsTable.tableId }}
477
- pagination={{ page, pageSize: 20, total, totalPages, onPageChange: setPage }}
480
+ pagination={{ page, pageSize: 20, total, totalPages, totalIsCapped, onPageChange: setPage }}
478
481
  isLoading={isLoading}
479
482
  onRowClick={(row) => setSelectedId((current) => current === row.id ? null : row.id)}
480
483
  rowActions={(row) => (
@@ -1,9 +1,13 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { getGatewayAdapter, type WebhookEvent } from '@open-mercato/shared/modules/payment_gateways/types'
3
+ import { isTrustedWebhookDispatch } from '@open-mercato/shared/lib/queue/dispatchOrigin'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
5
  import type { IntegrationLogService } from '../../integrations/lib/log-service'
4
6
  import type { PaymentGatewayService } from './gateway-service'
5
7
  import { claimWebhookProcessing, releaseWebhookClaim } from './webhook-utils'
6
8
 
9
+ const logger = createLogger('payment_gateways').child({ component: 'webhook-processor' })
10
+
7
11
  export type PaymentGatewayWebhookJobPayload = {
8
12
  providerKey: string
9
13
  event: WebhookEvent
@@ -53,6 +57,13 @@ export async function processPaymentGatewayWebhookJob(
53
57
  ): Promise<void> {
54
58
  const { em, paymentGatewayService, integrationLogService } = deps
55
59
  const { providerKey, event } = payload
60
+ // Fail closed on untrusted dispatch origins (#5213): only jobs enqueued by the
61
+ // inbound webhook route (signature verified) may drive payment state. Jobs that
62
+ // arrive through any other path — e.g. a scheduled queue target — are dropped.
63
+ if (!isTrustedWebhookDispatch(payload)) {
64
+ logger.error('Dropping webhook job with missing or untrusted dispatch origin', { providerKey, eventType: event?.eventType ?? null })
65
+ return
66
+ }
56
67
  // Scope MUST come from the trusted route layer (derived from a verified GatewayTransaction).
57
68
  // Never fall back to attacker-controlled metadata on `event.data.metadata` — that was the
58
69
  // vector that allowed forged mock webhooks to mutate another tenant's payment state.
@@ -39,6 +39,7 @@ type RuleSetResponse = {
39
39
  items?: Array<Record<string, unknown>>
40
40
  total?: number
41
41
  totalPages?: number
42
+ totalIsCapped?: boolean
42
43
  }
43
44
 
44
45
  export default function PlannerAvailabilityRuleSetsPage() {
@@ -67,6 +68,7 @@ export default function PlannerAvailabilityRuleSetsPage() {
67
68
  const [page, setPage] = React.useState(1)
68
69
  const [total, setTotal] = React.useState(0)
69
70
  const [totalPages, setTotalPages] = React.useState(1)
71
+ const [totalIsCapped, setTotalIsCapped] = React.useState(false)
70
72
  const [sorting, setSorting] = React.useState<SortingState>([{ id: 'name', desc: false }])
71
73
  const [search, setSearch] = React.useState('')
72
74
  const [isLoading, setIsLoading] = React.useState(true)
@@ -120,6 +122,7 @@ export default function PlannerAvailabilityRuleSetsPage() {
120
122
  setRows(items.map(mapRuleSet))
121
123
  setTotal(typeof payload.total === 'number' ? payload.total : items.length)
122
124
  setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : Math.max(1, Math.ceil(items.length / PAGE_SIZE)))
125
+ setTotalIsCapped(payload?.totalIsCapped === true)
123
126
  } catch (error) {
124
127
  logger.error('Failed to list availability rule sets', { err: error })
125
128
  flash(labels.errors.load, 'error')
@@ -229,6 +232,7 @@ export default function PlannerAvailabilityRuleSetsPage() {
229
232
  pageSize: PAGE_SIZE,
230
233
  total,
231
234
  totalPages,
235
+ totalIsCapped,
232
236
  onPageChange: setPage,
233
237
  }}
234
238
  rowActions={(row) => (
@@ -32,6 +32,7 @@ type ResponsePayload = {
32
32
  page?: number
33
33
  pageSize?: number
34
34
  totalPages: number
35
+ totalIsCapped?: boolean
35
36
  }
36
37
 
37
38
  const statusVariant: StatusMap<PushDeliveryStatus> = {
@@ -59,6 +60,7 @@ export default function PushDeliveriesListPage() {
59
60
  const [page, setPage] = React.useState(1)
60
61
  const [total, setTotal] = React.useState(0)
61
62
  const [totalPages, setTotalPages] = React.useState(1)
63
+ const [totalIsCapped, setTotalIsCapped] = React.useState(false)
62
64
  const [isLoading, setIsLoading] = React.useState(true)
63
65
  const scopeVersion = useOrganizationScopeVersion()
64
66
  const t = useT()
@@ -156,6 +158,7 @@ export default function PushDeliveriesListPage() {
156
158
  setRows(Array.isArray(payload.items) ? payload.items : [])
157
159
  setTotal(payload.total || 0)
158
160
  setTotalPages(payload.totalPages || 1)
161
+ setTotalIsCapped(payload?.totalIsCapped === true)
159
162
  }
160
163
  } catch (error) {
161
164
  if (!cancelled) {
@@ -229,7 +232,7 @@ export default function PushDeliveriesListPage() {
229
232
  onFiltersClear={() => { setFilterValues({}); setPage(1) }}
230
233
  perspective={{ tableId: 'push_notifications.deliveries' }}
231
234
  onRowClick={(row) => { window.location.href = `/backend/push_notifications/${row.id}` }}
232
- pagination={{ page, pageSize: 50, total, totalPages, onPageChange: setPage }}
235
+ pagination={{ page, pageSize: 50, total, totalPages, totalIsCapped, onPageChange: setPage }}
233
236
  isLoading={isLoading}
234
237
  emptyState={t('push_notifications.deliveries.empty')}
235
238
  />
@@ -70,7 +70,7 @@ function DeviceField({ value, setValue, setFormValue, values, onState }: CrudCus
70
70
  let cancelled = false
71
71
  setLoading(true)
72
72
  const params = new URLSearchParams({ userId, pageSize: '50' })
73
- apiCall<{ items?: Array<{ id: string; device_id: string; platform: string; push_provider?: string | null }> }>(
73
+ apiCall<{ items?: Array<{ id: string; deviceId: string; platform: string; pushProvider?: string | null }> }>(
74
74
  `/api/devices/admin/devices?${params.toString()}`,
75
75
  { headers: { 'x-om-forbidden-redirect': '0' } },
76
76
  { fallback: null },
@@ -80,9 +80,9 @@ function DeviceField({ value, setValue, setFormValue, values, onState }: CrudCus
80
80
  if (cancelled) return
81
81
  const items = (call && call.ok ? call.result?.items : []) ?? []
82
82
  const opts = items
83
- .filter((d): d is { id: string; device_id: string; platform: string; push_provider?: string | null } =>
84
- !!d && typeof d.id === 'string' && !!d.push_provider)
85
- .map((d) => ({ id: d.id, label: `${d.device_id} · ${d.platform}${d.push_provider ? ` · ${d.push_provider}` : ''}`, platform: d.platform }))
83
+ .filter((d): d is { id: string; deviceId: string; platform: string; pushProvider?: string | null } =>
84
+ !!d && typeof d.id === 'string' && !!d.pushProvider)
85
+ .map((d) => ({ id: d.id, label: `${d.deviceId} · ${d.platform}${d.pushProvider ? ` · ${d.pushProvider}` : ''}`, platform: d.platform }))
86
86
  setDevices(opts)
87
87
  if (selected && !opts.some((o) => o.id === selected)) setValue('')
88
88
  })
@@ -1,4 +1,4 @@
1
- import type { QueryEngine, QueryOptions, QueryResult, QueryResultMeta, EncryptedSortRowCapWarning, FilterOp, Filter, QueryCustomFieldSource, PartialIndexWarning, QueryExtensionsConfig, Sort } from '@open-mercato/shared/lib/query/types'
1
+ import type { QueryEngine, QueryOptions, QueryResult, QueryResultMeta, EncryptedSortRowCapWarning, ListCountCapWarning, FilterOp, Filter, QueryCustomFieldSource, PartialIndexWarning, QueryExtensionsConfig, Sort } from '@open-mercato/shared/lib/query/types'
2
2
  import { SortDir } from '@open-mercato/shared/lib/query/types'
3
3
  import type { EntityId } from '@open-mercato/shared/modules/entities'
4
4
  import type { EntityManager } from '@mikro-orm/postgresql'
@@ -20,6 +20,8 @@ import {
20
20
  type ResolvedJoin,
21
21
  } from '@open-mercato/shared/lib/query/join-utils'
22
22
  import { resolveSearchConfig, type SearchConfig } from '@open-mercato/shared/lib/search/config'
23
+ import { isEncryptedLikeField, resolveEncryptedLikeFieldSet } from '@open-mercato/shared/lib/query/engine'
24
+ import { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'
23
25
  import {
24
26
  createSearchTokenAvailability,
25
27
  isSearchFilterOp,
@@ -32,6 +34,7 @@ import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
32
34
  import { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from '@open-mercato/shared/lib/query/query-extension-runner'
33
35
  import { warnOnCiphertextLikeFallback } from '@open-mercato/shared/lib/query/ciphertext-search-warning'
34
36
  import { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from '@open-mercato/shared/lib/query/encrypted-sort'
37
+ import { resolveListCountCap } from '@open-mercato/shared/lib/query/count-cap'
35
38
  import { mapWithConcurrency } from '@open-mercato/shared/lib/query/bounded-decrypt'
36
39
  import { createLogger } from '@open-mercato/shared/lib/logger'
37
40
  import { parseNumberWithDefault } from '@open-mercato/shared/lib/number'
@@ -148,13 +151,25 @@ type SearchRuntime = {
148
151
  organizationScope?: { ids: string[]; includeNull: boolean } | null
149
152
  tenantId?: string | null
150
153
  searchSources?: SearchTokenSource[]
154
+ /**
155
+ * Base-column fields whose stored value is ciphertext, so a like/ilike on them can only be
156
+ * answered via search tokens. A plaintext column keeps exact SQL ILIKE instead: the token
157
+ * rewrite is approximate -- it splits on non-alphanumerics and drops tokens shorter than
158
+ * minTokenLength, so a document-number search like "ZK 1/2026" degrades to the tokens
159
+ * {202, 2026} and matches every record from that year, and an all-short term like "ZK"
160
+ * produces no tokens and silently drops the predicate. `null`/absent = the encryption
161
+ * service could not answer (or a caller predates this field); keep the old rewrite then,
162
+ * because guessing "plaintext" would turn encrypted-column search into an
163
+ * ILIKE-on-ciphertext that matches nothing.
164
+ */
165
+ encryptedFields?: Set<string> | null
151
166
  /** Per-`query()` alias minter for `search_tokens` subqueries (see #2738). */
152
167
  mintAlias: () => string
153
168
  }
154
169
 
155
170
  type EncryptionResolver = () => {
156
171
  decryptEntityPayload?: (entityId: EntityId, payload: Record<string, unknown>, tenantId?: string | null, organizationId?: string | null) => Promise<Record<string, unknown>>
157
- getEncryptedFieldNames?: (entityId: EntityId, tenantId?: string | null, organizationId?: string | null) => Promise<readonly string[]>
172
+ getEncryptedFieldNames?: (entityId: EntityId, tenantId?: string | null, organizationId?: string | null, options?: { ignoreRuntimeHealth?: boolean }) => Promise<readonly string[]>
158
173
  isEnabled?: () => boolean
159
174
  } | null
160
175
 
@@ -496,6 +511,51 @@ export class HybridQueryEngine implements QueryEngine {
496
511
  ? await this.searchAvailability().anySourceHasTokens(searchSources, opts.tenantId ?? null, orgScope)
497
512
  : false
498
513
  const searchRuntime: SearchRuntime = { ...searchRuntimeBase, searchSources, enabled: searchEnabled && hasSearchTokens }
514
+ if (
515
+ searchRuntime.enabled &&
516
+ searchConfig.useIlikeForNonEncryptedFields === true &&
517
+ sourceSearchFilters.some((filter) => !String(filter.field).startsWith('cf:'))
518
+ ) {
519
+ // `ignoreRuntimeHealth` asks the on-disk question -- a column holds ciphertext even while
520
+ // the KMS is down -- so an outage keeps encrypted columns on the token path (#4622).
521
+ // `organizationId: null` is deliberate, not an omission: the service then unions in every
522
+ // organization's map (`fetchAllOrganizationFieldNames`), so a field any org encrypts stays
523
+ // on the token path -- a wider set fails safe. Passing the request's org instead would
524
+ // silently break encrypted-column search for orgs without their own map. That union is an
525
+ // UNCACHED `encryption_maps` read, one extra round-trip per searched list request.
526
+ try {
527
+ const encryptionService = this.getEncryptionService()
528
+ const readEncryptedFieldNames = encryptionService?.getEncryptedFieldNames?.bind(encryptionService)
529
+ if (readEncryptedFieldNames) {
530
+ searchRuntime.encryptedFields = await resolveEncryptedLikeFieldSet(
531
+ () => readEncryptedFieldNames(
532
+ entity as EntityId,
533
+ opts.tenantId ?? null,
534
+ null,
535
+ { ignoreRuntimeHealth: true },
536
+ ),
537
+ String(entity),
538
+ opts.tenantId ?? null,
539
+ )
540
+ } else if (isTenantDataEncryptionEnabled()) {
541
+ // Encryption is on but the service is unreachable (a swallowed DI failure looks
542
+ // exactly like "no service"): treat the map as UNKNOWN and keep the token rewrite,
543
+ // rather than guessing "plaintext" and running ILIKE against ciphertext.
544
+ searchRuntime.encryptedFields = null
545
+ } else {
546
+ // Encryption disabled: nothing is ciphertext at rest, exact ILIKE is always right.
547
+ searchRuntime.encryptedFields = new Set()
548
+ }
549
+ } catch (err) {
550
+ // The fallback is safe (the old rewrite-everything behavior), but taking it silently
551
+ // would hide that the gate has stopped working.
552
+ logger.warn('search: encrypted-field map unavailable; keeping the token rewrite for all columns', {
553
+ entity: String(entity),
554
+ error: err instanceof Error ? err.message : String(err),
555
+ })
556
+ searchRuntime.encryptedFields = null
557
+ }
558
+ }
499
559
  if (searchFilters.length) {
500
560
  this.logSearchDebug('search:init', {
501
561
  entity,
@@ -785,10 +845,9 @@ export class HybridQueryEngine implements QueryEngine {
785
845
  return next
786
846
  }
787
847
 
788
- // Also used by the optimized count path, which builds from a bare base table with
789
- // no index/custom-field joins. Emitting a cf predicate there would reference an
790
- // alias that query has never joined, so this stays safe only while
791
- // `canOptimizeCount` is false whenever any cf filter exists (see `hasCustomFieldFilters`).
848
+ // Also used by the count shape: cf leaves (and doc-based base leaves) reference
849
+ // the index rowset aliases, so any group containing one routes the whole
850
+ // disjunction inside the seeded-rowset EXISTS, where those aliases exist.
792
851
  const applyOrGroupedBaseFilters = (q: AnyBuilder): AnyBuilder => {
793
852
  if (orGroupFilters.length === 0 && orGroupCfFilters.length === 0) return q
794
853
  // `BaseFilter` here is just the normalized-leaf shape; the `cf` bucket holds
@@ -937,6 +996,101 @@ export class HybridQueryEngine implements QueryEngine {
937
996
  const hasCustomFieldFilters = cfFilters.length > 0
938
997
  const canOptimizeCount = !hasCustomFieldFilters && !hasNonBaseSearchSource
939
998
 
999
+ // ── Count shape (#4552 Phase 2) ─────────────────────────────────
1000
+ // The count query is rebuilt rather than derived from the display shape:
1001
+ // base scope + filters only. Predicates that read the left-joined index
1002
+ // rowset (cf filters, doc filters, or-groups with doc members, token
1003
+ // searches over joined sources) evaluate against the *same* rowset as the
1004
+ // display query, but confined inside a single correlated EXISTS built
1005
+ // from a one-row seed — so the per-base-row rowset is identical, yet a
1006
+ // semi-join cannot multiply base rows. The count therefore needs no
1007
+ // DISTINCT or GROUP BY, and an outer LIMIT actually bounds the scan (no
1008
+ // blocking aggregate between the LIMIT and the scan).
1009
+ const isRowsetDependentFilter = (filter: BaseFilter): boolean => {
1010
+ const baseField = resolveBaseColumn(String(filter.field))
1011
+ if (!baseField) return true
1012
+ // Token search may probe joined sources' record ids, which only exist
1013
+ // on the index rowset.
1014
+ if (
1015
+ (filter.op === 'like' || filter.op === 'ilike') &&
1016
+ searchRuntime.enabled &&
1017
+ typeof filter.value === 'string' &&
1018
+ hasNonBaseSearchSource
1019
+ ) return true
1020
+ return false
1021
+ }
1022
+ const rowsetRegularFilters = regularBaseFilters.filter((filter) => isRowsetDependentFilter(filter))
1023
+ const outerRegularFilters = regularBaseFilters.filter((filter) => !isRowsetDependentFilter(filter))
1024
+ // Or-groups are disjuncts of one $or, so they cannot be split between the
1025
+ // outer query and the rowset — one doc-dependent member moves them all in.
1026
+ const orGroupsRowsetDependent =
1027
+ orGroupCfFilters.length > 0 ||
1028
+ orGroupFilters.some((filter) => isRowsetDependentFilter(filter))
1029
+ const hasInnerTypedCfSource = preparedCfSources.some((source) =>
1030
+ ((opts.customFieldSources ?? []).find((s) => s && (s.alias ?? undefined) === source.alias)?.join?.type ?? 'left') === 'inner')
1031
+ const needsIndexRowset =
1032
+ hasCustomFieldFilters ||
1033
+ rowsetRegularFilters.length > 0 ||
1034
+ orGroupsRowsetDependent ||
1035
+ hasInnerTypedCfSource
1036
+
1037
+ const applyCountShape = async (q: AnyBuilder): Promise<AnyBuilder> => {
1038
+ let next = applyBaseScope(q)
1039
+ for (const filter of outerRegularFilters) {
1040
+ const baseField = resolveBaseColumn(String(filter.field))
1041
+ if (!baseField) continue
1042
+ next = this.applyColumnFilter(next, qualify(baseField), filter, {
1043
+ ...searchRuntime, entity, field: String(filter.field), recordIdColumn: qualify('id'),
1044
+ })
1045
+ }
1046
+ if (!orGroupsRowsetDependent) next = applyOrGroupedBaseFilters(next)
1047
+ if (needsIndexRowset) {
1048
+ // One seed row per base row, then the display query's own join
1049
+ // builders — the rowset inside the EXISTS is the display rowset.
1050
+ // Built from `db` (not the where-callback's expression builder),
1051
+ // matching applyJoinFilters' detached-subquery idiom; correlated
1052
+ // references to the outer base alias resolve at compile time.
1053
+ let rowset: AnyBuilder = db
1054
+ .selectFrom(sql`(select 1)`.as('om_count_seed'))
1055
+ .select(sql<number>`1`.as('one'))
1056
+ rowset = applyEntityIndexesJoin(rowset)
1057
+ rowset = applyCustomFieldSourceJoins(rowset)
1058
+ rowset = applyCfFilters(rowset)
1059
+ for (const filter of rowsetRegularFilters) {
1060
+ const baseField = resolveBaseColumn(String(filter.field))
1061
+ if (!baseField) {
1062
+ rowset = this.applyIndexDocFilterFromAlias(
1063
+ rowset, 'ei', entity, String(filter.field), filter.op, filter.value, qualify('id'), searchRuntime,
1064
+ )
1065
+ continue
1066
+ }
1067
+ rowset = this.applyColumnFilter(rowset, qualify(baseField), filter, {
1068
+ ...searchRuntime, entity, field: String(filter.field), recordIdColumn: qualify('id'),
1069
+ })
1070
+ }
1071
+ if (orGroupsRowsetDependent) rowset = applyOrGroupedBaseFilters(rowset)
1072
+ const capturedRowset = rowset
1073
+ next = next.where((eb: any) => eb.exists(capturedRowset))
1074
+ }
1075
+ next = await applyJoinFilters({
1076
+ db,
1077
+ baseTable,
1078
+ builder: next,
1079
+ joinMap,
1080
+ joinFilters,
1081
+ aliasTables,
1082
+ qualifyBase: (column) => qualify(column),
1083
+ applyAliasScope: async (target: any, alias: string) => applyAliasScopes(target as AnyBuilder, alias),
1084
+ applyFilterOp: (target, column, op, value) => applyJoinFilterOpFn(target as AnyBuilder, column, op, value),
1085
+ applyJoinFilterOp: async (target, filter, qualified, join) => {
1086
+ const applied = await applyJoinSearchFilterOp(target as AnyBuilder, filter, qualified, join)
1087
+ return { applied, builder: target }
1088
+ },
1089
+ columnExists: (tbl, column) => this.columnExists(tbl, column),
1090
+ })
1091
+ return next
1092
+ }
1093
+
940
1094
  // Selection (for data query)
941
1095
  const selectFieldSet = new Set<string>((opts.fields && opts.fields.length) ? opts.fields.map(String) : Array.from(columns.keys()))
942
1096
  if (requiresPlaintextSort) {
@@ -997,33 +1151,23 @@ export class HybridQueryEngine implements QueryEngine {
997
1151
  const pageSize = opts.page?.pageSize ?? 20
998
1152
  const sqlDebugEnabled = this.isSqlDebugEnabled()
999
1153
 
1000
- let total: number
1154
+ const countCap = resolveListCountCap()
1001
1155
 
1002
- if (canOptimizeCount) {
1003
- // Optimized count: apply only base-scope + regular filters + or-group filters (no index joins).
1004
- const optimizedRoot = db.selectFrom(`${baseTable} as b` as any)
1005
- let countCore = applyBaseScope(optimizedRoot)
1006
- countCore = applyRegularBaseFilters(countCore)
1007
- countCore = applyOrGroupedBaseFilters(countCore)
1008
- // joinFilters still need to be re-applied in the optimized path
1009
- countCore = await applyJoinFilters({
1010
- db,
1011
- baseTable,
1012
- builder: countCore,
1013
- joinMap,
1014
- joinFilters,
1015
- aliasTables,
1016
- qualifyBase: (column) => qualify(column),
1017
- applyAliasScope: async (target: any, alias: string) => applyAliasScopes(target as AnyBuilder, alias),
1018
- applyFilterOp: (target, column, op, value) => applyJoinFilterOpFn(target as AnyBuilder, column, op, value),
1019
- applyJoinFilterOp: async (target, filter, qualified, join) => {
1020
- const applied = await applyJoinSearchFilterOp(target as AnyBuilder, filter, qualified, join)
1021
- return { applied, builder: target }
1022
- },
1023
- columnExists: (tbl, column) => this.columnExists(tbl, column),
1024
- })
1025
- const sub = countCore.select(sql.ref(qualify('id')).as('id')).groupBy(qualify('id')).as('sq')
1026
- const countQuery = db.selectFrom(sub as any).select(sql<string>`count(*)`.as('count'))
1156
+ // Both count paths build the same rebuilt shape; for `canOptimizeCount`
1157
+ // queries `needsIndexRowset` is false, so the shape degenerates to
1158
+ // base-scope + filters exactly as the optimized path always had.
1159
+ // `wasOptimizable` carries NO control flow — it only labels the debug
1160
+ // timing so operators can keep telling the two shapes apart.
1161
+ // TODO(2026-08-12): remove `canOptimizeCount` once the shape convergence
1162
+ // has soaked (tracked in the #4552 spec as the Phase 2 follow-up).
1163
+ const runBoundedCount = async (wasOptimizable: boolean): Promise<{ total: number; warning?: ListCountCapWarning }> => {
1164
+ const countRoot = db.selectFrom(`${baseTable} as b` as any)
1165
+ const shape = await applyCountShape(countRoot)
1166
+ const countQuery = countCap !== null
1167
+ ? db
1168
+ .selectFrom(shape.select(sql<number>`1`.as('one')).limit(countCap + 1).as('om_count_probe') as any)
1169
+ .select(sql<string>`count(*)`.as('count'))
1170
+ : shape.select(sql<string>`count(*)`.as('count'))
1027
1171
  if (debugEnabled && sqlDebugEnabled) {
1028
1172
  const compiled = countQuery.compile()
1029
1173
  this.debug('query:sql:count', { entity, sql: compiled.sql, bindings: compiled.parameters })
@@ -1031,25 +1175,19 @@ export class HybridQueryEngine implements QueryEngine {
1031
1175
  const countRow = await this.captureSqlTiming(
1032
1176
  'query:sql:count', entity,
1033
1177
  () => countQuery.executeTakeFirst(),
1034
- { optimized: true }, profiler,
1178
+ { optimized: wasOptimizable }, profiler,
1035
1179
  )
1036
- total = this.parseCount(countRow)
1037
- } else {
1038
- const countRoot = db.selectFrom(`${baseTable} as b` as any)
1039
- const countBuilder = (await applyQueryShape(countRoot))
1040
- .select(sql<string>`count(distinct ${sql.ref(qualify('id'))})`.as('count'))
1041
- if (debugEnabled && sqlDebugEnabled) {
1042
- const compiled = countBuilder.compile()
1043
- this.debug('query:sql:count', { entity, sql: compiled.sql, bindings: compiled.parameters })
1180
+ const probed = this.parseCount(countRow)
1181
+ if (countCap !== null && probed > countCap) {
1182
+ return { total: countCap, warning: { entity, cap: countCap } }
1044
1183
  }
1045
- const countRow = await this.captureSqlTiming(
1046
- 'query:sql:count', entity,
1047
- () => countBuilder.executeTakeFirst(),
1048
- { optimized: false }, profiler,
1049
- )
1050
- total = this.parseCount(countRow)
1184
+ return { total: probed }
1051
1185
  }
1052
1186
 
1187
+ const counted = await runBoundedCount(canOptimizeCount)
1188
+ const total: number = counted.total
1189
+ const listCountCapWarning: ListCountCapWarning | undefined = counted.warning
1190
+
1053
1191
  const dekKeyCache = new Map<string | null, string | null>()
1054
1192
 
1055
1193
  const decryptRow = async (item: Record<string, unknown>): Promise<Record<string, unknown>> => {
@@ -1100,24 +1238,29 @@ export class HybridQueryEngine implements QueryEngine {
1100
1238
  const sortFieldNames = Array.from(new Set(['id', ...scopeFieldNames, ...resolvedSorts.map((s) => String(s.field))]))
1101
1239
  phase1 = applySelection(phase1, sortFieldNames)
1102
1240
  if (cap !== null) {
1103
- phase1 = phase1.limit(cap).orderBy(qualify('id'), 'asc' as any)
1241
+ // Probe one row past the cap: truncation is detected from the candidate
1242
+ // scan itself, not by comparing against `total` — which may itself be
1243
+ // capped (`OM_LIST_COUNT_CAP`) and would then never exceed the sort cap.
1244
+ phase1 = phase1.limit(cap + 1).orderBy(qualify('id'), 'asc' as any)
1104
1245
  }
1105
1246
  if (debugEnabled && sqlDebugEnabled) {
1106
1247
  const compiled = phase1.compile()
1107
1248
  this.debug('query:sql:data:phase1', { entity, sql: compiled.sql, bindings: compiled.parameters })
1108
1249
  }
1109
- const candidateRows = await this.captureSqlTiming(
1250
+ const candidateRowsRaw = await this.captureSqlTiming(
1110
1251
  'query:sql:data:phase1', entity,
1111
1252
  () => phase1.execute(),
1112
1253
  { phase: 1 }, profiler,
1113
1254
  ) as Record<string, unknown>[]
1255
+ const sortTruncated = cap !== null && candidateRowsRaw.length > cap
1256
+ const candidateRows = sortTruncated && cap !== null ? candidateRowsRaw.slice(0, cap) : candidateRowsRaw
1114
1257
  const decryptedCandidates = await mapWithConcurrency(candidateRows, DECRYPT_CONCURRENCY, decryptRow)
1115
1258
  const orderedCandidates = sortRowsInMemory(decryptedCandidates, resolvedSorts)
1116
1259
  const pageIds = orderedCandidates
1117
1260
  .slice((page - 1) * pageSize, page * pageSize)
1118
1261
  .map((row) => row.id)
1119
1262
 
1120
- if (cap !== null && total > cap) {
1263
+ if (sortTruncated && cap !== null) {
1121
1264
  encryptedSortRowCapWarning = {
1122
1265
  entity,
1123
1266
  sortFields: resolvedSorts.map((s) => String(s.field)),
@@ -1173,10 +1316,11 @@ export class HybridQueryEngine implements QueryEngine {
1173
1316
 
1174
1317
  const typedItems = items as unknown as T[]
1175
1318
  let result: QueryResult<T> = { items: typedItems, page, pageSize, total }
1176
- if (partialIndexWarning || encryptedSortRowCapWarning) {
1319
+ if (partialIndexWarning || encryptedSortRowCapWarning || listCountCapWarning) {
1177
1320
  const meta: QueryResultMeta = {}
1178
1321
  if (partialIndexWarning) meta.partialIndexWarning = partialIndexWarning
1179
1322
  if (encryptedSortRowCapWarning) meta.encryptedSortRowCapWarning = encryptedSortRowCapWarning
1323
+ if (listCountCapWarning) meta.listCountCapWarning = listCountCapWarning
1180
1324
  result.meta = meta
1181
1325
  }
1182
1326
 
@@ -1743,11 +1887,13 @@ export class HybridQueryEngine implements QueryEngine {
1743
1887
  return this.buildIndexDocFilterExpression(eb, 'ei', entity, fieldName, filter.op, filter.value, 'b.id', searchRuntime)
1744
1888
  }
1745
1889
  // For like/ilike with active search-tokens, route through hashed-token EXISTS subquery
1746
- // so encrypted-at-rest columns can still be searched.
1890
+ // so encrypted-at-rest columns can still be searched. Plaintext base columns keep exact
1891
+ // SQL ILIKE -- see SearchRuntime.encryptedFields.
1747
1892
  if (
1748
1893
  (filter.op === 'like' || filter.op === 'ilike') &&
1749
1894
  searchRuntime?.enabled &&
1750
- typeof filter.value === 'string'
1895
+ typeof filter.value === 'string' &&
1896
+ (searchRuntime.encryptedFields == null || isEncryptedLikeField(searchRuntime.encryptedFields, fieldName))
1751
1897
  ) {
1752
1898
  const tokens = tokenizeText(String(filter.value), searchRuntime.config)
1753
1899
  if (tokens.hashes.length) {
@@ -1771,10 +1917,14 @@ export class HybridQueryEngine implements QueryEngine {
1771
1917
  )
1772
1918
  }
1773
1919
  }
1774
- // Tokenizer produced no hashes (e.g. value too short). Match the regular-base-filter
1775
- // path's behavior of skipping the predicate (no filter), which is preferable to
1776
- // silently turning into a plain `ilike` against an encrypted column.
1777
- return sql<boolean>`true`
1920
+ // Tokenizer produced no hashes (e.g. value too short) or no source is usable. For a
1921
+ // column KNOWN to be encrypted, `false` is the honest answer for an OR leaf -- `true`
1922
+ // would widen the whole disjunction to match everything, on exactly the columns ILIKE
1923
+ // cannot serve. Every other case (gate off, custom-entity runtime, resolution failure)
1924
+ // keeps the legacy predicate-skipping `true`.
1925
+ return searchRuntime?.encryptedFields != null && isEncryptedLikeField(searchRuntime.encryptedFields, fieldName)
1926
+ ? sql<boolean>`false`
1927
+ : sql<boolean>`true`
1778
1928
  }
1779
1929
  return this.buildColumnFilterExpression(eb, qualify(baseField), filter.op, filter.value)
1780
1930
  }
@@ -1853,6 +2003,9 @@ export class HybridQueryEngine implements QueryEngine {
1853
2003
  const hasSearchTokens = searchEnabled && hasSearchFilter(normalizedFilters)
1854
2004
  ? await this.searchAvailability().hasTokens(entity, opts.tenantId ?? null, orgScope)
1855
2005
  : false
2006
+ // `encryptedFields` is deliberately NOT resolved here: custom-entity rows live in the
2007
+ // `entity_indexes` doc store, whose fields the base-column encryption map does not describe,
2008
+ // so the ILIKE gate stays inert on this path and like/ilike keeps its previous semantics.
1856
2009
  const searchRuntime: SearchRuntime = {
1857
2010
  enabled: searchEnabled && hasSearchTokens,
1858
2011
  config: searchConfig,
@@ -1968,17 +2121,35 @@ export class HybridQueryEngine implements QueryEngine {
1968
2121
  const page = opts.page?.page ?? 1
1969
2122
  const pageSize = opts.page?.pageSize ?? 20
1970
2123
 
2124
+ // Single-table count: `applyScope` adds only WHERE predicates (cf filters
2125
+ // included — no join), and doc storage holds one row per record within a
2126
+ // scope, so `count(*)` needs no DISTINCT and — when the cap is active — a
2127
+ // LIMIT on the row-producing inner query bounds the scan (#4552 Phase 2).
2128
+ const countCap = resolveListCountCap()
1971
2129
  const root = db.selectFrom(`custom_entities_storage as ${alias}`)
1972
- const countQuery = applyScope(root).select(sql<string>`count(distinct ${sql.ref(`${alias}.entity_id`)})`.as('count'))
2130
+ const countShape = applyScope(root)
2131
+ const countQuery = countCap !== null
2132
+ ? db
2133
+ .selectFrom(countShape.select(sql<number>`1`.as('one')).limit(countCap + 1).as('om_count_probe') as any)
2134
+ .select(sql<string>`count(*)`.as('count'))
2135
+ : countShape.select(sql<string>`count(*)`.as('count'))
1973
2136
  const countRow = await countQuery.executeTakeFirst()
1974
- const total = this.parseCount(countRow)
2137
+ const probed = this.parseCount(countRow)
2138
+ let total = probed
2139
+ let listCountCapWarning: ListCountCapWarning | undefined
2140
+ if (countCap !== null && probed > countCap) {
2141
+ total = countCap
2142
+ listCountCapWarning = { entity: entity as EntityId, cap: countCap }
2143
+ }
1975
2144
 
1976
2145
  let dataQuery = applyScope(db.selectFrom(`custom_entities_storage as ${alias}`))
1977
2146
  dataQuery = applySelection(dataQuery)
1978
2147
  dataQuery = applySort(dataQuery)
1979
2148
  dataQuery = dataQuery.limit(pageSize).offset((page - 1) * pageSize)
1980
2149
  const items = await dataQuery.execute()
1981
- return { items, page, pageSize, total }
2150
+ const result: QueryResult<T> = { items, page, pageSize, total }
2151
+ if (listCountCapWarning) result.meta = { listCountCapWarning }
2152
+ return result
1982
2153
  }
1983
2154
 
1984
2155
  private async tableExists(table: string): Promise<boolean> {
@@ -2384,7 +2555,11 @@ export class HybridQueryEngine implements QueryEngine {
2384
2555
  if (
2385
2556
  (filter.op === 'like' || filter.op === 'ilike') &&
2386
2557
  search?.enabled &&
2387
- typeof filter.value === 'string'
2558
+ typeof filter.value === 'string' &&
2559
+ // Plaintext base columns keep exact SQL ILIKE -- see SearchRuntime.encryptedFields.
2560
+ // Membership runs across name-shape candidates: maps may declare `displayName` while the
2561
+ // filter carries the column name `display_name`.
2562
+ (search.encryptedFields == null || isEncryptedLikeField(search.encryptedFields, search.field))
2388
2563
  ) {
2389
2564
  const tokens = tokenizeText(String(filter.value), search.config)
2390
2565
  const hashes = tokens.hashes
@@ -2413,10 +2588,23 @@ export class HybridQueryEngine implements QueryEngine {
2413
2588
  })
2414
2589
  return q
2415
2590
  }
2591
+ // Hashes exist but no usable search source: same reasoning as the no-hash branch below --
2592
+ // a KNOWN-encrypted column must fail closed rather than drop the predicate (which would
2593
+ // return the full list on exactly the columns ILIKE cannot serve).
2594
+ if (search.encryptedFields != null && isEncryptedLikeField(search.encryptedFields, search.field)) {
2595
+ return q.where(sql<boolean>`false`)
2596
+ }
2416
2597
  } else {
2417
2598
  this.logSearchDebug('search:skip-empty-hashes', {
2418
2599
  entity: search.entity, field: search.field, value: filter.value,
2419
2600
  })
2601
+ // A column KNOWN to be encrypted has no way to match the term except the token index:
2602
+ // dropping the predicate would return every row for a term merely too short to tokenize.
2603
+ // Every other case (gate off, custom-entity runtime, resolution failure) keeps the
2604
+ // legacy behavior of skipping the predicate.
2605
+ if (search.encryptedFields != null && isEncryptedLikeField(search.encryptedFields, search.field)) {
2606
+ return q.where(sql<boolean>`false`)
2607
+ }
2420
2608
  }
2421
2609
  return q
2422
2610
  }