@hotmeshio/long-tail 0.29.0 → 0.30.0

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 (255) hide show
  1. package/build/api/roles.js +4 -0
  2. package/build/api/scan-codes/choice.js +6 -3
  3. package/build/api/scan-codes/context.d.ts +7 -1
  4. package/build/api/scan-codes/context.js +24 -0
  5. package/build/api/scan-codes/execute.js +4 -2
  6. package/build/api/scan-codes/present.d.ts +3 -1
  7. package/build/api/scan-codes/present.js +25 -12
  8. package/build/routes/oauth.js +9 -6
  9. package/build/services/oauth/providers/github.js +2 -1
  10. package/build/services/oauth/providers/google.js +3 -3
  11. package/build/services/oauth/providers/microsoft.js +4 -1
  12. package/build/services/oauth/providers/mock.js +1 -1
  13. package/build/services/role/scan-done.d.ts +7 -0
  14. package/build/services/role/scan-done.js +36 -0
  15. package/build/services/role/seed.js +4 -0
  16. package/build/services/role/types.d.ts +11 -0
  17. package/build/services/role/types.js +4 -0
  18. package/build/services/scan-code/validate.js +50 -0
  19. package/build/tsconfig.tsbuildinfo +1 -1
  20. package/build/types/index.d.ts +2 -2
  21. package/build/types/index.js +3 -1
  22. package/build/types/oauth.d.ts +1 -0
  23. package/build/types/scan-choice.d.ts +8 -0
  24. package/build/types/scan-code/constants.d.ts +3 -0
  25. package/build/types/scan-code/constants.js +4 -1
  26. package/build/types/scan-code/response.d.ts +14 -0
  27. package/build/types/scan-code/rule.d.ts +11 -0
  28. package/dashboard/dist/assets/{AdminDashboard-DRE0hSDS.js → AdminDashboard-Bjf_7-S6.js} +2 -2
  29. package/dashboard/dist/assets/{AdminDashboard-DRE0hSDS.js.map → AdminDashboard-Bjf_7-S6.js.map} +1 -1
  30. package/dashboard/dist/assets/{AgentConfigPage-BuflQGgy.js → AgentConfigPage-CEGk5Gbv.js} +6 -6
  31. package/dashboard/dist/assets/{AgentConfigPage-BuflQGgy.js.map → AgentConfigPage-CEGk5Gbv.js.map} +1 -1
  32. package/dashboard/dist/assets/{AgentDetailPage-BiL9FB7i.js → AgentDetailPage-C52Oi1_f.js} +3 -3
  33. package/dashboard/dist/assets/{AgentDetailPage-BiL9FB7i.js.map → AgentDetailPage-C52Oi1_f.js.map} +1 -1
  34. package/dashboard/dist/assets/{AgentsPage-C4ZxCNxt.js → AgentsPage-CohqxaWS.js} +2 -2
  35. package/dashboard/dist/assets/{AgentsPage-C4ZxCNxt.js.map → AgentsPage-CohqxaWS.js.map} +1 -1
  36. package/dashboard/dist/assets/{AvailableEscalationsPage-B5p8QJvI.js → AvailableEscalationsPage-DXpRnOVd.js} +2 -2
  37. package/dashboard/dist/assets/{AvailableEscalationsPage-B5p8QJvI.js.map → AvailableEscalationsPage-DXpRnOVd.js.map} +1 -1
  38. package/dashboard/dist/assets/{BotPicker-B7V77PnY.js → BotPicker-DPglulL6.js} +2 -2
  39. package/dashboard/dist/assets/{BotPicker-B7V77PnY.js.map → BotPicker-DPglulL6.js.map} +1 -1
  40. package/dashboard/dist/assets/{BulkAssignModal-DcGVMQqm.js → BulkAssignModal-DWNR3FJJ.js} +2 -2
  41. package/dashboard/dist/assets/{BulkAssignModal-DcGVMQqm.js.map → BulkAssignModal-DWNR3FJJ.js.map} +1 -1
  42. package/dashboard/dist/assets/{CapabilitiesPage-DmbC1pEs.js → CapabilitiesPage-ChBBD0IX.js} +2 -2
  43. package/dashboard/dist/assets/{CapabilitiesPage-DmbC1pEs.js.map → CapabilitiesPage-ChBBD0IX.js.map} +1 -1
  44. package/dashboard/dist/assets/{CollapsibleSection-C81S1qic.js → CollapsibleSection-QAXKTYc6.js} +2 -2
  45. package/dashboard/dist/assets/{CollapsibleSection-C81S1qic.js.map → CollapsibleSection-QAXKTYc6.js.map} +1 -1
  46. package/dashboard/dist/assets/{ConfirmDeleteModal-3RFyWN9Q.js → ConfirmDeleteModal-J9xc_gVZ.js} +2 -2
  47. package/dashboard/dist/assets/{ConfirmDeleteModal-3RFyWN9Q.js.map → ConfirmDeleteModal-J9xc_gVZ.js.map} +1 -1
  48. package/dashboard/dist/assets/{ConnectedAppsPage-B0D2OvTD.js → ConnectedAppsPage-BKhsc_jJ.js} +2 -2
  49. package/dashboard/dist/assets/{ConnectedAppsPage-B0D2OvTD.js.map → ConnectedAppsPage-BKhsc_jJ.js.map} +1 -1
  50. package/dashboard/dist/assets/{CountdownTimer-iAG28fzG.js → CountdownTimer-C4hP64lF.js} +2 -2
  51. package/dashboard/dist/assets/{CountdownTimer-iAG28fzG.js.map → CountdownTimer-C4hP64lF.js.map} +1 -1
  52. package/dashboard/dist/assets/{CredentialsPage-DoFwNvFv.js → CredentialsPage-DUfgsEbI.js} +2 -2
  53. package/dashboard/dist/assets/{CredentialsPage-DoFwNvFv.js.map → CredentialsPage-DUfgsEbI.js.map} +1 -1
  54. package/dashboard/dist/assets/{CronLabel-C1Bb2wo4.js → CronLabel-CBc3Tvau.js} +2 -2
  55. package/dashboard/dist/assets/{CronLabel-C1Bb2wo4.js.map → CronLabel-CBc3Tvau.js.map} +1 -1
  56. package/dashboard/dist/assets/{DropZone-Cl_Flb16.js → DropZone-DefYTYQy.js} +2 -2
  57. package/dashboard/dist/assets/{DropZone-Cl_Flb16.js.map → DropZone-DefYTYQy.js.map} +1 -1
  58. package/dashboard/dist/assets/{ElapsedCell-DZs1I1JW.js → ElapsedCell-V27Y2Ksb.js} +2 -2
  59. package/dashboard/dist/assets/{ElapsedCell-DZs1I1JW.js.map → ElapsedCell-V27Y2Ksb.js.map} +1 -1
  60. package/dashboard/dist/assets/{EntityTimelinePanel-Du1O6Oly.js → EntityTimelinePanel-BA4MWXkB.js} +2 -2
  61. package/dashboard/dist/assets/{EntityTimelinePanel-Du1O6Oly.js.map → EntityTimelinePanel-BA4MWXkB.js.map} +1 -1
  62. package/dashboard/dist/assets/{EscalationListSchemaPage-CrpBhC0e.js → EscalationListSchemaPage-B20HUAlO.js} +2 -2
  63. package/dashboard/dist/assets/{EscalationListSchemaPage-CrpBhC0e.js.map → EscalationListSchemaPage-B20HUAlO.js.map} +1 -1
  64. package/dashboard/dist/assets/{EscalationSchemaPage-Co59lcuD.js → EscalationSchemaPage-Cen9HGYG.js} +2 -2
  65. package/dashboard/dist/assets/{EscalationSchemaPage-Co59lcuD.js.map → EscalationSchemaPage-Cen9HGYG.js.map} +1 -1
  66. package/dashboard/dist/assets/{EscalationTimeline-B_fzNsX5.js → EscalationTimeline-DGNgcMm0.js} +2 -2
  67. package/dashboard/dist/assets/{EscalationTimeline-B_fzNsX5.js.map → EscalationTimeline-DGNgcMm0.js.map} +1 -1
  68. package/dashboard/dist/assets/{EscalationTitleSelect-C2Goebjo.js → EscalationTitleSelect-BhsflGiC.js} +2 -2
  69. package/dashboard/dist/assets/{EscalationTitleSelect-C2Goebjo.js.map → EscalationTitleSelect-BhsflGiC.js.map} +1 -1
  70. package/dashboard/dist/assets/{EscalationsOverview-Cgmrl8Zc.js → EscalationsOverview-C2nc-Pgv.js} +2 -2
  71. package/dashboard/dist/assets/{EscalationsOverview-Cgmrl8Zc.js.map → EscalationsOverview-C2nc-Pgv.js.map} +1 -1
  72. package/dashboard/dist/assets/{EventTable-BdEiu3Fv.js → EventTable-BvNxHKy1.js} +2 -2
  73. package/dashboard/dist/assets/{EventTable-BdEiu3Fv.js.map → EventTable-BvNxHKy1.js.map} +1 -1
  74. package/dashboard/dist/assets/{FilterBar-CzKYBOMC.js → FilterBar-22FDO0R_.js} +2 -2
  75. package/dashboard/dist/assets/{FilterBar-CzKYBOMC.js.map → FilterBar-22FDO0R_.js.map} +1 -1
  76. package/dashboard/dist/assets/{GraphInvokePage-CcDFhY53.js → GraphInvokePage-BAyfJ-PY.js} +2 -2
  77. package/dashboard/dist/assets/{GraphInvokePage-CcDFhY53.js.map → GraphInvokePage-BAyfJ-PY.js.map} +1 -1
  78. package/dashboard/dist/assets/{HomePage-Xahqcdu9.js → HomePage-CtjG2kGn.js} +2 -2
  79. package/dashboard/dist/assets/{HomePage-Xahqcdu9.js.map → HomePage-CtjG2kGn.js.map} +1 -1
  80. package/dashboard/dist/assets/{LinePage-DKwhGunp.js → LinePage-DPe-vP3-.js} +3 -3
  81. package/dashboard/dist/assets/{LinePage-DKwhGunp.js.map → LinePage-DPe-vP3-.js.map} +1 -1
  82. package/dashboard/dist/assets/{LinesPage-BXZVO4MC.js → LinesPage-BCSfYkuL.js} +2 -2
  83. package/dashboard/dist/assets/{LinesPage-BXZVO4MC.js.map → LinesPage-BCSfYkuL.js.map} +1 -1
  84. package/dashboard/dist/assets/{ListToolbar-orQsOdl_.js → ListToolbar-6siR0Hao.js} +2 -2
  85. package/dashboard/dist/assets/{ListToolbar-orQsOdl_.js.map → ListToolbar-6siR0Hao.js.map} +1 -1
  86. package/dashboard/dist/assets/{McpOverview-pSnadtSM.js → McpOverview-Cbu2CXlQ.js} +2 -2
  87. package/dashboard/dist/assets/{McpOverview-pSnadtSM.js.map → McpOverview-Cbu2CXlQ.js.map} +1 -1
  88. package/dashboard/dist/assets/{McpQueryDetailPage-BCAJ3rKO.js → McpQueryDetailPage--1FsnGuM.js} +2 -2
  89. package/dashboard/dist/assets/{McpQueryDetailPage-BCAJ3rKO.js.map → McpQueryDetailPage--1FsnGuM.js.map} +1 -1
  90. package/dashboard/dist/assets/{McpQueryPage-CRyXeRp5.js → McpQueryPage-CPUUMIiY.js} +2 -2
  91. package/dashboard/dist/assets/{McpQueryPage-CRyXeRp5.js.map → McpQueryPage-CPUUMIiY.js.map} +1 -1
  92. package/dashboard/dist/assets/{McpRunDetailPage-BHppCztI.js → McpRunDetailPage-BDCv-c1o.js} +2 -2
  93. package/dashboard/dist/assets/{McpRunDetailPage-BHppCztI.js.map → McpRunDetailPage-BDCv-c1o.js.map} +1 -1
  94. package/dashboard/dist/assets/{McpRunsPage-Cd9yCT-_.js → McpRunsPage-Df4yOclO.js} +2 -2
  95. package/dashboard/dist/assets/{McpRunsPage-Cd9yCT-_.js.map → McpRunsPage-Df4yOclO.js.map} +1 -1
  96. package/dashboard/dist/assets/{NamespacePill-OTZD2hzt.js → NamespacePill-CeSfHH-j.js} +2 -2
  97. package/dashboard/dist/assets/{NamespacePill-OTZD2hzt.js.map → NamespacePill-CeSfHH-j.js.map} +1 -1
  98. package/dashboard/dist/assets/{OperationsPage-BtnthZgj.js → OperationsPage-BYUGFgPp.js} +2 -2
  99. package/dashboard/dist/assets/{OperationsPage-BtnthZgj.js.map → OperationsPage-BYUGFgPp.js.map} +1 -1
  100. package/dashboard/dist/assets/{OperatorDashboard-B4_OBhYh.js → OperatorDashboard-Dd1EhTZe.js} +2 -2
  101. package/dashboard/dist/assets/{OperatorDashboard-B4_OBhYh.js.map → OperatorDashboard-Dd1EhTZe.js.map} +1 -1
  102. package/dashboard/dist/assets/{PageHeader-Hx8APPwE.js → PageHeader-DiGWAQlR.js} +2 -2
  103. package/dashboard/dist/assets/{PageHeader-Hx8APPwE.js.map → PageHeader-DiGWAQlR.js.map} +1 -1
  104. package/dashboard/dist/assets/{PageHeaderWithStats-BEQYYBJv.js → PageHeaderWithStats-DwnKQ06j.js} +2 -2
  105. package/dashboard/dist/assets/{PageHeaderWithStats-BEQYYBJv.js.map → PageHeaderWithStats-DwnKQ06j.js.map} +1 -1
  106. package/dashboard/dist/assets/{PersonaDetailPage-BAYjM9qf.js → PersonaDetailPage-BCx3Ey2_.js} +2 -2
  107. package/dashboard/dist/assets/{PersonaDetailPage-BAYjM9qf.js.map → PersonaDetailPage-BCx3Ey2_.js.map} +1 -1
  108. package/dashboard/dist/assets/{PersonasPage-6HsOp-1y.js → PersonasPage--Ez0YeHh.js} +2 -2
  109. package/dashboard/dist/assets/{PersonasPage-6HsOp-1y.js.map → PersonasPage--Ez0YeHh.js.map} +1 -1
  110. package/dashboard/dist/assets/{PortalPage-6OMr7KtJ.js → PortalPage-BmBmheBp.js} +2 -2
  111. package/dashboard/dist/assets/{PortalPage-6OMr7KtJ.js.map → PortalPage-BmBmheBp.js.map} +1 -1
  112. package/dashboard/dist/assets/{ProcessDetailPage-AidH_h7z.js → ProcessDetailPage-DBj4VEnP.js} +2 -2
  113. package/dashboard/dist/assets/{ProcessDetailPage-AidH_h7z.js.map → ProcessDetailPage-DBj4VEnP.js.map} +1 -1
  114. package/dashboard/dist/assets/{ProcessesListPage-BWKoKbXk.js → ProcessesListPage-CD5NRIBK.js} +2 -2
  115. package/dashboard/dist/assets/{ProcessesListPage-BWKoKbXk.js.map → ProcessesListPage-CD5NRIBK.js.map} +1 -1
  116. package/dashboard/dist/assets/{ResolverForm-BaSvhLoT.js → ResolverForm-Baxwv_IG.js} +2 -2
  117. package/dashboard/dist/assets/{ResolverForm-BaSvhLoT.js.map → ResolverForm-Baxwv_IG.js.map} +1 -1
  118. package/dashboard/dist/assets/RoleDetailPage-6ukNyycs.js +8 -0
  119. package/dashboard/dist/assets/RoleDetailPage-6ukNyycs.js.map +1 -0
  120. package/dashboard/dist/assets/{RolePill-R5_ctGFy.js → RolePill-D3hkAdKf.js} +2 -2
  121. package/dashboard/dist/assets/{RolePill-R5_ctGFy.js.map → RolePill-D3hkAdKf.js.map} +1 -1
  122. package/dashboard/dist/assets/{RolesPage-T2hH_MV5.js → RolesPage-DRs2XeSd.js} +2 -2
  123. package/dashboard/dist/assets/{RolesPage-T2hH_MV5.js.map → RolesPage-DRs2XeSd.js.map} +1 -1
  124. package/dashboard/dist/assets/{RosterWidget-DG6Rcx4P.js → RosterWidget-BPBPSa1k.js} +2 -2
  125. package/dashboard/dist/assets/{RosterWidget-DG6Rcx4P.js.map → RosterWidget-BPBPSa1k.js.map} +1 -1
  126. package/dashboard/dist/assets/{RunAsSelector-M2h9U6PP.js → RunAsSelector-Br9FiMwP.js} +2 -2
  127. package/dashboard/dist/assets/{RunAsSelector-M2h9U6PP.js.map → RunAsSelector-Br9FiMwP.js.map} +1 -1
  128. package/dashboard/dist/assets/{ScanCodesPage-lB_WxoZw.js → ScanCodesPage-BXENWoZb.js} +2 -2
  129. package/dashboard/dist/assets/{ScanCodesPage-lB_WxoZw.js.map → ScanCodesPage-BXENWoZb.js.map} +1 -1
  130. package/dashboard/dist/assets/{ScanSchemeDetailPage-C3VLg61H.js → ScanSchemeDetailPage-CaM4rUBV.js} +2 -2
  131. package/dashboard/dist/assets/{ScanSchemeDetailPage-C3VLg61H.js.map → ScanSchemeDetailPage-CaM4rUBV.js.map} +1 -1
  132. package/dashboard/dist/assets/ScanStationPage-CmJKgWy_.js +2 -0
  133. package/dashboard/dist/assets/ScanStationPage-CmJKgWy_.js.map +1 -0
  134. package/dashboard/dist/assets/{ScopeBadge-BXZ084nD.js → ScopeBadge-DGz79rmG.js} +2 -2
  135. package/dashboard/dist/assets/{ScopeBadge-BXZ084nD.js.map → ScopeBadge-DGz79rmG.js.map} +1 -1
  136. package/dashboard/dist/assets/{StickyPagination-BdjseWXr.js → StickyPagination-DX5CPgrX.js} +2 -2
  137. package/dashboard/dist/assets/{StickyPagination-BdjseWXr.js.map → StickyPagination-DX5CPgrX.js.map} +1 -1
  138. package/dashboard/dist/assets/{StreamMessageDetail-DJCBxETh.js → StreamMessageDetail-3mWw3_M5.js} +2 -2
  139. package/dashboard/dist/assets/{StreamMessageDetail-DJCBxETh.js.map → StreamMessageDetail-3mWw3_M5.js.map} +1 -1
  140. package/dashboard/dist/assets/{SwimlaneTimeline-DiXm9jIk.js → SwimlaneTimeline-C_pIcWBJ.js} +2 -2
  141. package/dashboard/dist/assets/{SwimlaneTimeline-DiXm9jIk.js.map → SwimlaneTimeline-C_pIcWBJ.js.map} +1 -1
  142. package/dashboard/dist/assets/{TagInput-DjPUDp2M.js → TagInput-DaIdhv3h.js} +2 -2
  143. package/dashboard/dist/assets/{TagInput-DjPUDp2M.js.map → TagInput-DaIdhv3h.js.map} +1 -1
  144. package/dashboard/dist/assets/{TaskDetailPage-m8CQFN4w.js → TaskDetailPage-DthMVBZO.js} +2 -2
  145. package/dashboard/dist/assets/{TaskDetailPage-m8CQFN4w.js.map → TaskDetailPage-DthMVBZO.js.map} +1 -1
  146. package/dashboard/dist/assets/{TaskQueuePill-D-4Ynf3K.js → TaskQueuePill-sUA1nZe-.js} +2 -2
  147. package/dashboard/dist/assets/{TaskQueuePill-D-4Ynf3K.js.map → TaskQueuePill-sUA1nZe-.js.map} +1 -1
  148. package/dashboard/dist/assets/{TasksListPage-DHBSUhZC.js → TasksListPage-MtawUpuU.js} +2 -2
  149. package/dashboard/dist/assets/{TasksListPage-DHBSUhZC.js.map → TasksListPage-MtawUpuU.js.map} +1 -1
  150. package/dashboard/dist/assets/{TimeAgo-Cq3bKUd-.js → TimeAgo-DIziYk-v.js} +2 -2
  151. package/dashboard/dist/assets/{TimeAgo-Cq3bKUd-.js.map → TimeAgo-DIziYk-v.js.map} +1 -1
  152. package/dashboard/dist/assets/{TimestampCell-e5gRR_FY.js → TimestampCell-Dn5M-Eey.js} +2 -2
  153. package/dashboard/dist/assets/{TimestampCell-e5gRR_FY.js.map → TimestampCell-Dn5M-Eey.js.map} +1 -1
  154. package/dashboard/dist/assets/{ToolPill-GsKQ80CO.js → ToolPill-CpBAFdEX.js} +2 -2
  155. package/dashboard/dist/assets/{ToolPill-GsKQ80CO.js.map → ToolPill-CpBAFdEX.js.map} +1 -1
  156. package/dashboard/dist/assets/{ToolTestPanel-Bg0ss9oN.js → ToolTestPanel-DaBrgPML.js} +2 -2
  157. package/dashboard/dist/assets/{ToolTestPanel-Bg0ss9oN.js.map → ToolTestPanel-DaBrgPML.js.map} +1 -1
  158. package/dashboard/dist/assets/{TopicDetailPage-DhPiCfOO.js → TopicDetailPage-zNE3EigP.js} +2 -2
  159. package/dashboard/dist/assets/{TopicDetailPage-DhPiCfOO.js.map → TopicDetailPage-zNE3EigP.js.map} +1 -1
  160. package/dashboard/dist/assets/{TopicsPage-BqZaVv3d.js → TopicsPage-Cj0ugdie.js} +2 -2
  161. package/dashboard/dist/assets/{TopicsPage-BqZaVv3d.js.map → TopicsPage-Cj0ugdie.js.map} +1 -1
  162. package/dashboard/dist/assets/{UserName-C6ZNHHKN.js → UserName-t3PbLYfd.js} +2 -2
  163. package/dashboard/dist/assets/{UserName-C6ZNHHKN.js.map → UserName-t3PbLYfd.js.map} +1 -1
  164. package/dashboard/dist/assets/{WorkflowExecutionPage-BUqIhtcr.js → WorkflowExecutionPage-CZ2c5QLL.js} +2 -2
  165. package/dashboard/dist/assets/{WorkflowExecutionPage-BUqIhtcr.js.map → WorkflowExecutionPage-CZ2c5QLL.js.map} +1 -1
  166. package/dashboard/dist/assets/{WorkflowPill-D7244fKp.js → WorkflowPill-CKHip3hH.js} +2 -2
  167. package/dashboard/dist/assets/{WorkflowPill-D7244fKp.js.map → WorkflowPill-CKHip3hH.js.map} +1 -1
  168. package/dashboard/dist/assets/{WorkflowsDashboard-BKxhC_hT.js → WorkflowsDashboard-DLb71Sa2.js} +2 -2
  169. package/dashboard/dist/assets/{WorkflowsDashboard-BKxhC_hT.js.map → WorkflowsDashboard-DLb71Sa2.js.map} +1 -1
  170. package/dashboard/dist/assets/{WorkflowsOverview-018s2q_p.js → WorkflowsOverview-Wz6RzxHJ.js} +2 -2
  171. package/dashboard/dist/assets/{WorkflowsOverview-018s2q_p.js.map → WorkflowsOverview-Wz6RzxHJ.js.map} +1 -1
  172. package/dashboard/dist/assets/{YamlWorkflowDetailPage-C2zcXDr1.js → YamlWorkflowDetailPage-CcRAN_ey.js} +2 -2
  173. package/dashboard/dist/assets/{YamlWorkflowDetailPage-C2zcXDr1.js.map → YamlWorkflowDetailPage-CcRAN_ey.js.map} +1 -1
  174. package/dashboard/dist/assets/{YamlWorkflowsPage-aVcNZNu6.js → YamlWorkflowsPage-B4C9fiYU.js} +2 -2
  175. package/dashboard/dist/assets/{YamlWorkflowsPage-aVcNZNu6.js.map → YamlWorkflowsPage-B4C9fiYU.js.map} +1 -1
  176. package/dashboard/dist/assets/{agents-DRaaJUue.js → agents-BvRjwrof.js} +2 -2
  177. package/dashboard/dist/assets/{agents-DRaaJUue.js.map → agents-BvRjwrof.js.map} +1 -1
  178. package/dashboard/dist/assets/{bots-C4N90NCj.js → bots-qx7qPwFo.js} +2 -2
  179. package/dashboard/dist/assets/{bots-C4N90NCj.js.map → bots-qx7qPwFo.js.map} +1 -1
  180. package/dashboard/dist/assets/{capabilities-BoRWifSC.js → capabilities-DJrUEeew.js} +2 -2
  181. package/dashboard/dist/assets/{capabilities-BoRWifSC.js.map → capabilities-DJrUEeew.js.map} +1 -1
  182. package/dashboard/dist/assets/{controlplane-DGZAxi7J.js → controlplane-D82a5cfq.js} +2 -2
  183. package/dashboard/dist/assets/{controlplane-DGZAxi7J.js.map → controlplane-D82a5cfq.js.map} +1 -1
  184. package/dashboard/dist/assets/{escalation-columns-DJbfsH_m.js → escalation-columns-Dmm8LY_f.js} +2 -2
  185. package/dashboard/dist/assets/{escalation-columns-DJbfsH_m.js.map → escalation-columns-Dmm8LY_f.js.map} +1 -1
  186. package/dashboard/dist/assets/{escalation-items-C8iJZEA8.js → escalation-items-C3ko1DCb.js} +2 -2
  187. package/dashboard/dist/assets/{escalation-items-C8iJZEA8.js.map → escalation-items-C3ko1DCb.js.map} +1 -1
  188. package/dashboard/dist/assets/{index-BuSUevzO.js → index-BAxEeqfF.js} +2 -2
  189. package/dashboard/dist/assets/{index-BuSUevzO.js.map → index-BAxEeqfF.js.map} +1 -1
  190. package/dashboard/dist/assets/{index-DhKGp4QK.js → index-BCSeHOZH.js} +2 -2
  191. package/dashboard/dist/assets/{index-DhKGp4QK.js.map → index-BCSeHOZH.js.map} +1 -1
  192. package/dashboard/dist/assets/{index-DeyA88jq.js → index-BJrsQM-E.js} +2 -2
  193. package/dashboard/dist/assets/{index-DeyA88jq.js.map → index-BJrsQM-E.js.map} +1 -1
  194. package/dashboard/dist/assets/{index-CXXc9sUr.js → index-BVxqddYr.js} +2 -2
  195. package/dashboard/dist/assets/{index-CXXc9sUr.js.map → index-BVxqddYr.js.map} +1 -1
  196. package/dashboard/dist/assets/{index-DIU89TX3.js → index-C59ZyWOo.js} +2 -2
  197. package/dashboard/dist/assets/{index-DIU89TX3.js.map → index-C59ZyWOo.js.map} +1 -1
  198. package/dashboard/dist/assets/{index-CyL-6CAa.js → index-CNDnGdvT.js} +2 -2
  199. package/dashboard/dist/assets/{index-CyL-6CAa.js.map → index-CNDnGdvT.js.map} +1 -1
  200. package/dashboard/dist/assets/{index-pzbGpihI.js → index-CSr5q355.js} +25 -25
  201. package/dashboard/dist/assets/index-CSr5q355.js.map +1 -0
  202. package/dashboard/dist/assets/{index-DMKLnxt8.js → index-CXnmU0TS.js} +2 -2
  203. package/dashboard/dist/assets/{index-DMKLnxt8.js.map → index-CXnmU0TS.js.map} +1 -1
  204. package/dashboard/dist/assets/{index-CjjRxpVe.js → index-C_-FoMo9.js} +2 -2
  205. package/dashboard/dist/assets/{index-CjjRxpVe.js.map → index-C_-FoMo9.js.map} +1 -1
  206. package/dashboard/dist/assets/index-C_dp4WgY.css +1 -0
  207. package/dashboard/dist/assets/{index-3HaFP00M.js → index-D-vNL8Hm.js} +2 -2
  208. package/dashboard/dist/assets/{index-3HaFP00M.js.map → index-D-vNL8Hm.js.map} +1 -1
  209. package/dashboard/dist/assets/{index-BFxHIfQ_.js → index-DOkHnsLK.js} +2 -2
  210. package/dashboard/dist/assets/{index-BFxHIfQ_.js.map → index-DOkHnsLK.js.map} +1 -1
  211. package/dashboard/dist/assets/{index-BhS9XSy8.js → index-U5gC9Xgq.js} +2 -2
  212. package/dashboard/dist/assets/{index-BhS9XSy8.js.map → index-U5gC9Xgq.js.map} +1 -1
  213. package/dashboard/dist/assets/{index-CeeSFnl-.js → index-f4-pKYcm.js} +2 -2
  214. package/dashboard/dist/assets/{index-CeeSFnl-.js.map → index-f4-pKYcm.js.map} +1 -1
  215. package/dashboard/dist/assets/{knowledge-BWo3OCJc.js → knowledge-i7_nXSbf.js} +2 -2
  216. package/dashboard/dist/assets/{knowledge-BWo3OCJc.js.map → knowledge-i7_nXSbf.js.map} +1 -1
  217. package/dashboard/dist/assets/{machines-LEYY_yar.js → machines-DfSNshLg.js} +2 -2
  218. package/dashboard/dist/assets/{machines-LEYY_yar.js.map → machines-DfSNshLg.js.map} +1 -1
  219. package/dashboard/dist/assets/{mcp-_0kFRcyT.js → mcp-dCLNwcC8.js} +2 -2
  220. package/dashboard/dist/assets/{mcp-_0kFRcyT.js.map → mcp-dCLNwcC8.js.map} +1 -1
  221. package/dashboard/dist/assets/{mcp-query-BzmN0lc5.js → mcp-query-wmbaGRYi.js} +2 -2
  222. package/dashboard/dist/assets/{mcp-query-BzmN0lc5.js.map → mcp-query-wmbaGRYi.js.map} +1 -1
  223. package/dashboard/dist/assets/{personas-kE4PMrgo.js → personas-B_cJA5UG.js} +2 -2
  224. package/dashboard/dist/assets/{personas-kE4PMrgo.js.map → personas-B_cJA5UG.js.map} +1 -1
  225. package/dashboard/dist/assets/{pipelines-DYDNaypF.js → pipelines-ClUmuwmY.js} +2 -2
  226. package/dashboard/dist/assets/{pipelines-DYDNaypF.js.map → pipelines-ClUmuwmY.js.map} +1 -1
  227. package/dashboard/dist/assets/{tasks-ClJ-mbj9.js → tasks-BE97GrLr.js} +2 -2
  228. package/dashboard/dist/assets/{tasks-ClJ-mbj9.js.map → tasks-BE97GrLr.js.map} +1 -1
  229. package/dashboard/dist/assets/{topics-oHX4vbqe.js → topics-Dk0Y3kZP.js} +2 -2
  230. package/dashboard/dist/assets/{topics-oHX4vbqe.js.map → topics-Dk0Y3kZP.js.map} +1 -1
  231. package/dashboard/dist/assets/{useDebouncedValue-C97KADrj.js → useDebouncedValue-BLGuHd_v.js} +2 -2
  232. package/dashboard/dist/assets/{useDebouncedValue-C97KADrj.js.map → useDebouncedValue-BLGuHd_v.js.map} +1 -1
  233. package/dashboard/dist/assets/{useNamespace-Kje0Rn7j.js → useNamespace-Cj7t8eQd.js} +2 -2
  234. package/dashboard/dist/assets/{useNamespace-Kje0Rn7j.js.map → useNamespace-Cj7t8eQd.js.map} +1 -1
  235. package/dashboard/dist/assets/{useYamlActivityEvents-DMz-HKFv.js → useYamlActivityEvents-C7eurSkD.js} +2 -2
  236. package/dashboard/dist/assets/{useYamlActivityEvents-DMz-HKFv.js.map → useYamlActivityEvents-C7eurSkD.js.map} +1 -1
  237. package/dashboard/dist/assets/{validate-hUqjWN63.js → validate-BypZfZ8R.js} +2 -2
  238. package/dashboard/dist/assets/{validate-hUqjWN63.js.map → validate-BypZfZ8R.js.map} +1 -1
  239. package/dashboard/dist/assets/{vendor-icons-fxBvvHBi.js → vendor-icons-en0_uSRD.js} +191 -186
  240. package/dashboard/dist/assets/vendor-icons-en0_uSRD.js.map +1 -0
  241. package/dashboard/dist/assets/{yaml-workflows-64vfmKJx.js → yaml-workflows-CnMKmznF.js} +2 -2
  242. package/dashboard/dist/assets/{yaml-workflows-64vfmKJx.js.map → yaml-workflows-CnMKmznF.js.map} +1 -1
  243. package/dashboard/dist/index.html +3 -3
  244. package/docs/api/http/roles.md +1 -1
  245. package/docs/dashboard.md +2 -2
  246. package/docs/oauth-and-delegation.md +5 -1
  247. package/docs/scan-codes.md +56 -0
  248. package/package.json +1 -1
  249. package/dashboard/dist/assets/RoleDetailPage-D_30TDVh.js +0 -8
  250. package/dashboard/dist/assets/RoleDetailPage-D_30TDVh.js.map +0 -1
  251. package/dashboard/dist/assets/ScanStationPage-CVET1_4r.js +0 -2
  252. package/dashboard/dist/assets/ScanStationPage-CVET1_4r.js.map +0 -1
  253. package/dashboard/dist/assets/index-B9uv-euR.css +0 -1
  254. package/dashboard/dist/assets/index-pzbGpihI.js.map +0 -1
  255. package/dashboard/dist/assets/vendor-icons-fxBvvHBi.js.map +0 -1
@@ -50,6 +50,7 @@ exports.deleteRole = deleteRole;
50
50
  const roleService = __importStar(require("../services/role"));
51
51
  const types_1 = require("../services/role/types");
52
52
  const badge_grant_1 = require("../services/role/badge-grant");
53
+ const scan_done_1 = require("../services/role/scan-done");
53
54
  const facet_sql_1 = require("../services/escalation/facet-sql");
54
55
  const list_schema_1 = require("../shared/list-schema");
55
56
  /**
@@ -296,6 +297,9 @@ async function updateRole(input) {
296
297
  const grantError = (0, badge_grant_1.badgeGrantError)(input.properties?.[types_1.ROLE_PROPERTY_KEYS.BADGE_GRANT]);
297
298
  if (grantError)
298
299
  return { status: 400, error: grantError };
300
+ const scanDoneProblem = (0, scan_done_1.scanDoneError)(input.properties?.[types_1.ROLE_PROPERTY_KEYS.SCAN_DONE]);
301
+ if (scanDoneProblem)
302
+ return { status: 400, error: scanDoneProblem };
299
303
  const listSchemaError = input.list_schema != null ? (0, list_schema_1.listSchemaRelationsError)(input.list_schema) : null;
300
304
  if (listSchemaError)
301
305
  return { status: 400, error: `list_schema: ${listSchemaError}` };
@@ -41,6 +41,7 @@ const identity_1 = require("./identity");
41
41
  const grant_1 = require("./grant");
42
42
  const helpers_1 = require("../escalations/helpers");
43
43
  const context_1 = require("./context");
44
+ const present_1 = require("./present");
44
45
  const choice_roles_1 = require("./choice-roles");
45
46
  const x_lt_actions_1 = require("../../shared/form-validation/x-lt-actions");
46
47
  const locate_1 = require("./locate");
@@ -134,12 +135,14 @@ async function executeScanChoice(input, auth) {
134
135
  // same double-actor race a second scan loses.
135
136
  const located = await (0, locate_1.locateForStep)(step, ctx, 2);
136
137
  const presented = located?.escalations.find((e) => e.id === input.escalationId);
137
- if (!presented) {
138
+ if (!presented || !(0, present_1.choiceOffered)(choice, presented)) {
138
139
  return decorate((0, context_1.conflict)('the item is no longer in the presented state'), ctx, input);
139
140
  }
140
- // 7. Dispatch through the same atomic executors a direct scan uses.
141
+ // 7. Dispatch through the same atomic executors a direct scan uses; the
142
+ // choice's done copy reads the row it acted on.
141
143
  const synthesized = { query: step.query, verb: choice.verb, params: choice.params };
142
- const result = decorate(await (0, verbs_1.dispatchChoiceVerb)(synthesized, ctx, presented), ctx, input);
144
+ const tpl = { ...(0, context_1.templateContext)(ctx), item: presented.metadata ?? {} };
145
+ const result = decorate((0, context_1.withDone)(await (0, verbs_1.dispatchChoiceVerb)(synthesized, ctx, presented), choice, tpl), ctx, input);
143
146
  // 8. Settle the badge: refund a spend whose act did not land
144
147
  const acting = await (0, grant_1.settleGrant)(ctx, result);
145
148
  if (acting && result.data)
@@ -78,4 +78,10 @@ export declare function templated<T>(step: ScanStep, ctx: StepContext, render: (
78
78
  * bags the copy may read (the container a subject step located, a fill's
79
79
  * batch); an unreadable token renders empty rather than failing the act.
80
80
  */
81
- export declare function withDone(result: LTApiResult<ScanExecuteResponse>, step: ScanStep, tpl: scanCodeService.ScanTemplateContext): LTApiResult<ScanExecuteResponse>;
81
+ export declare function withDone(result: LTApiResult<ScanExecuteResponse>, step: Pick<ScanStep, 'done'>, tpl: scanCodeService.ScanTemplateContext): LTApiResult<ScanExecuteResponse>;
82
+ /**
83
+ * Attach the step's `follow` to a write that completed the held subject: the
84
+ * code the station replays and the target it waits for, so the subject's
85
+ * next row is held without scanning it again.
86
+ */
87
+ export declare function withFollow(result: LTApiResult<ScanExecuteResponse>, step: Pick<ScanStep, 'follow'>, ctx: StepContext): LTApiResult<ScanExecuteResponse>;
@@ -47,6 +47,7 @@ exports.notPrimed = notPrimed;
47
47
  exports.refused = refused;
48
48
  exports.templated = templated;
49
49
  exports.withDone = withDone;
50
+ exports.withFollow = withFollow;
50
51
  const scanCodeService = __importStar(require("../../services/scan-code"));
51
52
  const escalationService = __importStar(require("../../services/escalation"));
52
53
  const types_1 = require("../../types");
@@ -160,3 +161,26 @@ function withDone(result, step, tpl) {
160
161
  return result;
161
162
  return { ...result, data: { ...data, done: { markdown: scanCodeService.renderScanCopy(step.done.markdown, tpl) } } };
162
163
  }
164
+ /**
165
+ * Attach the step's `follow` to a write that completed the held subject: the
166
+ * code the station replays and the target it waits for, so the subject's
167
+ * next row is held without scanning it again.
168
+ */
169
+ function withFollow(result, step, ctx) {
170
+ const data = result.data;
171
+ const subject = ctx.subject;
172
+ if (!step.follow || !subject || !data || data.outcome !== types_1.SCAN_OUTCOMES.EXECUTED || !data.clearSubject)
173
+ return result;
174
+ return {
175
+ ...result,
176
+ data: {
177
+ ...data,
178
+ follow: {
179
+ code: subject.code,
180
+ roles: step.follow.roles,
181
+ waitSeconds: step.follow.waitSeconds ?? types_1.SCAN_FOLLOW_WAIT_DEFAULT_SECONDS,
182
+ target: { facet: subject.scheme.target_facet, value: subject.parsed.target },
183
+ },
184
+ },
185
+ };
186
+ }
@@ -114,8 +114,10 @@ async function executeScanCode(input, auth) {
114
114
  let result = null;
115
115
  for (let i = 0; i < rule.steps.length && !result; i++) {
116
116
  const stepResult = await executeStep(rule.steps[i], ctx);
117
- if (stepResult)
118
- result = decorate((0, context_1.withDone)(stepResult, rule.steps[i], (0, context_1.templateContext)(ctx)), ctx, i);
117
+ if (stepResult) {
118
+ const step = rule.steps[i];
119
+ result = decorate((0, context_1.withFollow)((0, context_1.withDone)(stepResult, step, (0, context_1.templateContext)(ctx)), step, ctx), ctx, i);
120
+ }
119
121
  }
120
122
  // 4. Nothing matched: a stale subject, or the fallback screen
121
123
  if (!result) {
@@ -1,6 +1,8 @@
1
- import { type ScanExecuteResponse, type ScanStep } from '../../types';
1
+ import { type LTEscalationRecord, type ScanChoice, type ScanExecuteResponse, type ScanStep } from '../../types';
2
2
  import type { LTApiResult } from '../../types/sdk';
3
3
  import { type StepContext } from './context';
4
+ /** True when the row carries every facet value the choice is offered under. */
5
+ export declare function choiceOffered(choice: Pick<ScanChoice, 'when'>, row: LTEscalationRecord): boolean;
4
6
  /**
5
7
  * Locate the target and present the step's choices. Choices whose identity
6
8
  * requirement is unsatisfied come back withheld — the screen renders the
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.choiceOffered = choiceOffered;
36
37
  exports.presentStep = presentStep;
37
38
  const scanCodeService = __importStar(require("../../services/scan-code"));
38
39
  const types_1 = require("../../types");
@@ -41,6 +42,11 @@ const choice_roles_1 = require("./choice-roles");
41
42
  const locate_1 = require("./locate");
42
43
  const verbs_1 = require("./verbs");
43
44
  const context_1 = require("./context");
45
+ /** True when the row carries every facet value the choice is offered under. */
46
+ function choiceOffered(choice, row) {
47
+ const meta = (row.metadata ?? {});
48
+ return Object.entries(choice.when ?? {}).every(([key, value]) => meta[key] === value);
49
+ }
44
50
  /** The step's facts rendered against the presented row; unreadable tokens render empty. */
45
51
  function renderFacts(step, ctx, row) {
46
52
  if (!step.facts)
@@ -83,19 +89,26 @@ async function presentStep(step, ctx) {
83
89
  const roleHeld = await Promise.all((step.choices ?? []).map((choice) => (0, choice_roles_1.holdsChoiceRole)(choice, ctx.auth.userId)));
84
90
  const only = step.choices?.length === 1 ? step.choices[0] : undefined;
85
91
  const autoSelect = step.autoSelectSingle === true && only !== undefined && !only.confirm;
86
- if (autoSelect && (!only.requireActingIdentity || satisfied) && roleHeld[0]) {
87
- return (0, verbs_1.dispatchChoiceVerb)({ query: step.query, verb: only.verb, params: only.params }, ctx, escalation);
92
+ if (autoSelect && (!only.requireActingIdentity || satisfied) && roleHeld[0] && choiceOffered(only, escalation)) {
93
+ const tpl = { ...(0, context_1.templateContext)(ctx), item: escalation.metadata ?? {} };
94
+ return (0, context_1.withDone)(await (0, verbs_1.dispatchChoiceVerb)({ query: step.query, verb: only.verb, params: only.params }, ctx, escalation), only, tpl);
88
95
  }
89
- const choices = (step.choices ?? []).map((choice, index) => ({
90
- index,
91
- label: choice.label,
92
- verb: choice.verb,
93
- ...(choice.confirm ? { confirm: choice.confirm } : {}),
94
- ...(choice.requireActingIdentity ? { requireActingIdentity: true } : {}),
95
- ...(choice.roles?.length ? { roles: choice.roles } : {}),
96
- ...(choice.code ? { code: choice.code } : {}),
97
- withheld: (!!choice.requireActingIdentity && !satisfied) || !roleHeld[index],
98
- }));
96
+ // A choice the row's state does not offer, or one hidden from a person
97
+ // without its roles, is left off the screen; the index stays the config's.
98
+ const choices = (step.choices ?? []).flatMap((choice, index) => {
99
+ if (!choiceOffered(choice, escalation) || (choice.hidden && !roleHeld[index]))
100
+ return [];
101
+ return [{
102
+ index,
103
+ label: choice.label,
104
+ verb: choice.verb,
105
+ ...(choice.confirm ? { confirm: choice.confirm } : {}),
106
+ ...(choice.requireActingIdentity ? { requireActingIdentity: true } : {}),
107
+ ...(choice.roles?.length ? { roles: choice.roles } : {}),
108
+ ...(choice.code ? { code: choice.code } : {}),
109
+ withheld: (!!choice.requireActingIdentity && !satisfied) || !roleHeld[index],
110
+ }];
111
+ });
99
112
  return {
100
113
  status: 200,
101
114
  data: {
@@ -8,6 +8,8 @@ const oauth_1 = require("../services/oauth");
8
8
  const provisioning_1 = require("../services/oauth/provisioning");
9
9
  const user_1 = require("../services/user");
10
10
  const router = (0, http_1.Router)();
11
+ // What a refused person reads. The reason is logged, never shown.
12
+ const SIGN_IN_REFUSED = 'This account is not allowed to sign in here. Contact an administrator.';
11
13
  // ── OAuth startup config (set by initializeOAuth) ────────────────────────────
12
14
  let _oauthConfig = {
13
15
  autoProvision: true,
@@ -119,19 +121,20 @@ router.get('/:provider/callback', async (req, res) => {
119
121
  // Find or create the user
120
122
  let user = await (0, oauth_1.getUserByOAuthProvider)(provider, userInfo.providerUserId);
121
123
  if (!user && userInfo.email) {
122
- // Try matching by email
124
+ // A provider id meets an invited account once, by its verified email
123
125
  user = await (0, user_1.getUserByEmail)(userInfo.email);
126
+ if (user)
127
+ logger_1.loggerRegistry.info(`[oauth] bound ${provider} id to existing account ${user.id} by verified email`);
124
128
  }
125
129
  if (!user) {
126
130
  if (!_oauthConfig.autoProvision) {
127
- res.status(403).json({ error: 'No account found. Contact an administrator.' });
131
+ logger_1.loggerRegistry.warn(`[oauth] refused ${provider} sign-in: no account and auto-provision is off`);
132
+ res.redirect(`/login?error=${encodeURIComponent(SIGN_IN_REFUSED)}`);
128
133
  return;
129
134
  }
130
135
  if (!(0, provisioning_1.mayProvision)(userInfo.email, _oauthConfig.allowedDomains)) {
131
- logger_1.loggerRegistry.warn(`[oauth] refused self-provisioning for ${provider} sign-in outside allowed domains`);
132
- res.status(403).json({
133
- error: `Sign-in is limited to ${_oauthConfig.allowedDomains.join(', ')}. Contact an administrator.`,
134
- });
136
+ logger_1.loggerRegistry.warn(`[oauth] refused ${provider} sign-in: email domain not in allowedDomains`);
137
+ res.redirect(`/login?error=${encodeURIComponent(SIGN_IN_REFUSED)}`);
135
138
  return;
136
139
  }
137
140
  // Auto-provision new user
@@ -74,7 +74,8 @@ function createGitHubHandler(cfg) {
74
74
  ]);
75
75
  const user = await userRes.json();
76
76
  const emails = await emailRes.json().catch(() => []);
77
- const primary = emails.find((e) => e.primary)?.email || emails[0]?.email || null;
77
+ const verified = emails.filter((e) => e.verified === true);
78
+ const primary = verified.find((e) => e.primary)?.email || verified[0]?.email || null;
78
79
  return {
79
80
  provider: 'github',
80
81
  providerUserId: String(user.id),
@@ -75,14 +75,14 @@ function createGoogleHandler(cfg) {
75
75
  };
76
76
  },
77
77
  async fetchUserInfo(accessToken) {
78
- const res = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
78
+ const res = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
79
79
  headers: { Authorization: `Bearer ${accessToken}` },
80
80
  });
81
81
  const data = await res.json();
82
82
  return {
83
83
  provider: 'google',
84
- providerUserId: data.id,
85
- email: data.email || null,
84
+ providerUserId: data.sub || data.id,
85
+ email: data.email_verified === true && data.email ? data.email : null,
86
86
  displayName: data.name || null,
87
87
  raw: data,
88
88
  };
@@ -34,9 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.createMicrosoftHandler = createMicrosoftHandler;
37
+ const MULTI_TENANT_AUTHORITIES = new Set(['common', 'organizations', 'consumers']);
37
38
  function createMicrosoftHandler(cfg) {
38
39
  const redirectUri = cfg.redirectUri || '';
39
40
  const tenantId = process.env.OAUTH_MICROSOFT_TENANT_ID || 'common';
41
+ // Graph does not verify mail or the UPN. Only a single-tenant authority vouches for the account.
42
+ const singleTenant = !MULTI_TENANT_AUTHORITIES.has(tenantId);
40
43
  let _ms;
41
44
  async function getClient() {
42
45
  if (!_ms) {
@@ -79,7 +82,7 @@ function createMicrosoftHandler(cfg) {
79
82
  return {
80
83
  provider: 'microsoft',
81
84
  providerUserId: data.id,
82
- email: data.mail || data.userPrincipalName || null,
85
+ email: singleTenant ? data.mail || data.userPrincipalName || null : null,
83
86
  displayName: data.displayName || null,
84
87
  raw: data,
85
88
  };
@@ -66,7 +66,7 @@ function createMockHandler(cfg) {
66
66
  return {
67
67
  provider: 'mock',
68
68
  providerUserId: data.sub || data.id || 'mock-user',
69
- email: data.email || null,
69
+ email: data.email_verified === true && data.email ? data.email : null,
70
70
  displayName: data.name || null,
71
71
  raw: data,
72
72
  };
@@ -0,0 +1,7 @@
1
+ import { type RoleScanDone } from './types';
2
+ export declare const SCAN_DONE_RETURN_HOME = "home";
3
+ export declare const SCAN_DONE_AFTER_SECONDS_DEFAULT = 2;
4
+ /** The problem with a `properties.scan_done` value, or null when it is usable (or absent). */
5
+ export declare function scanDoneError(value: unknown): string | null;
6
+ /** The role's return policy from its properties bag, or null when it declares none (or an unusable one). */
7
+ export declare function readScanDone(properties: Record<string, unknown> | null | undefined): RoleScanDone | null;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SCAN_DONE_AFTER_SECONDS_DEFAULT = exports.SCAN_DONE_RETURN_HOME = void 0;
4
+ exports.scanDoneError = scanDoneError;
5
+ exports.readScanDone = readScanDone;
6
+ const types_1 = require("./types");
7
+ exports.SCAN_DONE_RETURN_HOME = 'home';
8
+ exports.SCAN_DONE_AFTER_SECONDS_DEFAULT = 2;
9
+ const AFTER_SECONDS_MAX = 60;
10
+ /** The problem with a `properties.scan_done` value, or null when it is usable (or absent). */
11
+ function scanDoneError(value) {
12
+ if (value === undefined || value === null)
13
+ return null;
14
+ if (typeof value !== 'object' || Array.isArray(value))
15
+ return 'properties.scan_done must be an object';
16
+ const done = value;
17
+ const known = new Set(['return', 'after_seconds']);
18
+ const extra = Object.keys(done).filter((k) => !known.has(k));
19
+ if (extra.length)
20
+ return `properties.scan_done has unknown key(s): ${extra.join(', ')}`;
21
+ if (typeof done.return !== 'string' || !(done.return === exports.SCAN_DONE_RETURN_HOME || done.return.startsWith('/'))) {
22
+ return `properties.scan_done.return must be '${exports.SCAN_DONE_RETURN_HOME}' or a dashboard-relative route`;
23
+ }
24
+ if (done.after_seconds !== undefined
25
+ && (!Number.isInteger(done.after_seconds) || done.after_seconds < 0 || done.after_seconds > AFTER_SECONDS_MAX)) {
26
+ return `properties.scan_done.after_seconds must be an integer from 0 to ${AFTER_SECONDS_MAX}`;
27
+ }
28
+ return null;
29
+ }
30
+ /** The role's return policy from its properties bag, or null when it declares none (or an unusable one). */
31
+ function readScanDone(properties) {
32
+ const value = properties?.[types_1.ROLE_PROPERTY_KEYS.SCAN_DONE];
33
+ if (value === undefined || value === null || scanDoneError(value))
34
+ return null;
35
+ return value;
36
+ }
@@ -9,6 +9,7 @@ const logger_1 = require("../../lib/logger");
9
9
  const index_1 = require("./index");
10
10
  const sql_1 = require("./sql");
11
11
  const badge_grant_1 = require("./badge-grant");
12
+ const scan_done_1 = require("./scan-done");
12
13
  const list_schema_1 = require("../../shared/list-schema");
13
14
  const types_1 = require("./types");
14
15
  /** Recorded on schema snapshots written by the startup apply. */
@@ -85,6 +86,9 @@ async function applyRoleConfig(cfg, codeOwned) {
85
86
  const grantError = (0, badge_grant_1.badgeGrantError)(cfg.properties?.[types_1.ROLE_PROPERTY_KEYS.BADGE_GRANT]);
86
87
  if (grantError)
87
88
  throw new Error(`role ${cfg.role}: ${grantError}`);
89
+ const scanDoneProblem = (0, scan_done_1.scanDoneError)(cfg.properties?.[types_1.ROLE_PROPERTY_KEYS.SCAN_DONE]);
90
+ if (scanDoneProblem)
91
+ throw new Error(`role ${cfg.role}: ${scanDoneProblem}`);
88
92
  const listSchemaError = cfg.list_schema != null ? (0, list_schema_1.listSchemaRelationsError)(cfg.list_schema) : null;
89
93
  if (listSchemaError)
90
94
  throw new Error(`role ${cfg.role}: list_schema: ${listSchemaError}`);
@@ -19,6 +19,9 @@ export interface EscalationChain {
19
19
  * A badge scanned on a device signed in as a member of this role mints
20
20
  * its acting grant under this policy instead of the badge scheme's own
21
21
  * (see api/scan-codes/badge-policy.ts).
22
+ * - SCAN_DONE: RoleScanDone — where a station locked to this role goes once
23
+ * a scan act completes its row: the kiosk home or a route, after a pause
24
+ * on the done copy.
22
25
  * - LINK_VARIABLES: RoleLinkVariable[] — metadata facet names members bind
23
26
  * per device. Pins may reference `{lt:name}` as a whole facet value
24
27
  * (e.g. `facets={"region":"{lt:region}"}`); the dashboard substitutes
@@ -32,7 +35,15 @@ export declare const ROLE_PROPERTY_KEYS: {
32
35
  readonly KIOSK: "kiosk";
33
36
  readonly LINK_VARIABLES: "link_variables";
34
37
  readonly BADGE_GRANT: "badge_grant";
38
+ readonly SCAN_DONE: "scan_done";
35
39
  };
40
+ /** Where a station returns after a scan act completes its row. */
41
+ export interface RoleScanDone {
42
+ /** 'home' is the kiosk home (portal or list); otherwise a dashboard-relative route. */
43
+ return: 'home' | string;
44
+ /** Seconds the done copy stays up first (0-60, default 2). */
45
+ after_seconds?: number;
46
+ }
36
47
  /** A station role's badge policy. Absent fields keep the badge scheme's value. */
37
48
  export interface RoleBadgeGrant {
38
49
  /** How long a badge scan stays primed (1-86400 s). */
@@ -18,6 +18,9 @@ exports.PORTAL_KEY = exports.PORTAL_LIMITS = exports.ROLE_PROPERTY_KEYS = void 0
18
18
  * A badge scanned on a device signed in as a member of this role mints
19
19
  * its acting grant under this policy instead of the badge scheme's own
20
20
  * (see api/scan-codes/badge-policy.ts).
21
+ * - SCAN_DONE: RoleScanDone — where a station locked to this role goes once
22
+ * a scan act completes its row: the kiosk home or a route, after a pause
23
+ * on the done copy.
21
24
  * - LINK_VARIABLES: RoleLinkVariable[] — metadata facet names members bind
22
25
  * per device. Pins may reference `{lt:name}` as a whole facet value
23
26
  * (e.g. `facets={"region":"{lt:region}"}`); the dashboard substitutes
@@ -31,6 +34,7 @@ exports.ROLE_PROPERTY_KEYS = {
31
34
  KIOSK: 'kiosk',
32
35
  LINK_VARIABLES: 'link_variables',
33
36
  BADGE_GRANT: 'badge_grant',
37
+ SCAN_DONE: 'scan_done',
34
38
  };
35
39
  /** Bounds for a role's portals: how many, and the matrix of each (rows of cells, each cell a pin). */
36
40
  exports.PORTAL_LIMITS = {
@@ -151,8 +151,57 @@ function assertValidChoices(choices, at) {
151
151
  throw new Error(`${cat}: duplicate code "${choice.code}"`);
152
152
  codes.add(choice.code);
153
153
  }
154
+ if (choice.done !== undefined) {
155
+ if (!choice.done || typeof choice.done !== 'object' || typeof choice.done.markdown !== 'string' || !choice.done.markdown) {
156
+ throw new Error(`${cat}: done.markdown is required`);
157
+ }
158
+ if (!types_1.SCAN_MUTATING_VERBS.includes(choice.verb)) {
159
+ throw new Error(`${cat}: done applies only to choices that write`);
160
+ }
161
+ }
162
+ if (choice.hidden !== undefined) {
163
+ if (typeof choice.hidden !== 'boolean')
164
+ throw new Error(`${cat}: hidden must be a boolean`);
165
+ if (choice.hidden && !choice.roles?.length)
166
+ throw new Error(`${cat}: hidden requires roles`);
167
+ }
168
+ if (choice.when !== undefined)
169
+ assertValidWhen(choice.when, cat);
154
170
  });
155
171
  }
172
+ /** A choice's `when`: facet keys to scalar values the row must carry. */
173
+ function assertValidWhen(when, at) {
174
+ if (!when || typeof when !== 'object' || Array.isArray(when) || Object.keys(when).length === 0) {
175
+ throw new Error(`${at}: when must be a non-empty object of facet values`);
176
+ }
177
+ for (const [key, value] of Object.entries(when)) {
178
+ if (!facet_sql_1.FACET_KEY.test(key))
179
+ throw new Error(`${at}: when key "${key}" is not a facet key`);
180
+ if (!['string', 'number', 'boolean'].includes(typeof value)) {
181
+ throw new Error(`${at}: when.${key} must be a string, number, or boolean`);
182
+ }
183
+ }
184
+ }
185
+ /** A step's `follow`: the roles the subject's next row may be in, and how long to wait for it. */
186
+ function assertValidFollow(step, at) {
187
+ const follow = step.follow;
188
+ if (follow === undefined)
189
+ return;
190
+ if (!follow || typeof follow !== 'object' || Array.isArray(follow))
191
+ throw new Error(`${at}: follow must be an object`);
192
+ if (!Array.isArray(follow.roles) || follow.roles.length === 0
193
+ || follow.roles.some((r) => typeof r !== 'string' || !r)) {
194
+ throw new Error(`${at}: follow.roles must be a non-empty array of role names`);
195
+ }
196
+ if (follow.waitSeconds !== undefined
197
+ && (!Number.isInteger(follow.waitSeconds) || follow.waitSeconds < 1 || follow.waitSeconds > types_1.SCAN_FOLLOW_WAIT_MAX_SECONDS)) {
198
+ throw new Error(`${at}: follow.waitSeconds must be an integer from 1 to ${types_1.SCAN_FOLLOW_WAIT_MAX_SECONDS}`);
199
+ }
200
+ if (!types_1.SCAN_MUTATING_VERBS.includes(step.verb))
201
+ throw new Error(`${at}: follow applies only to steps that write`);
202
+ if (!step.subject)
203
+ throw new Error(`${at}: follow applies only to steps that act on the held subject`);
204
+ }
156
205
  /**
157
206
  * Reject incoherent rule steps at write time: a rule that can never
158
207
  * execute its verb must fail the upsert, not the factory-floor scan.
@@ -247,6 +296,7 @@ function assertValidSteps(steps) {
247
296
  throw new Error(`${at}: done applies only to steps that write`);
248
297
  }
249
298
  }
299
+ assertValidFollow(step, at);
250
300
  assertValidTemplateTokens(step, at);
251
301
  if (step.query && step.query.roles && !Array.isArray(step.query.roles)) {
252
302
  throw new Error(`${at}: query.roles must be an array`);