@hotmeshio/long-tail 0.12.0 → 0.13.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 (257) hide show
  1. package/README.md +1 -0
  2. package/build/api/escalations/helpers.js +4 -2
  3. package/build/api/settings.js +3 -0
  4. package/build/services/agent/index.d.ts +11 -0
  5. package/build/services/agent/index.js +32 -0
  6. package/build/services/agent/sql.d.ts +7 -0
  7. package/build/services/agent/sql.js +32 -1
  8. package/build/services/agent/subscription-sql.d.ts +6 -0
  9. package/build/services/agent/subscription-sql.js +32 -1
  10. package/build/services/agent/subscriptions.d.ts +6 -0
  11. package/build/services/agent/subscriptions.js +23 -0
  12. package/build/services/config/index.d.ts +1 -1
  13. package/build/services/config/index.js +2 -1
  14. package/build/services/config/write.d.ts +10 -0
  15. package/build/services/config/write.js +49 -2
  16. package/build/services/domain/seed.d.ts +12 -5
  17. package/build/services/domain/seed.js +25 -6
  18. package/build/services/mcp/db.d.ts +16 -0
  19. package/build/services/mcp/db.js +24 -0
  20. package/build/services/mcp/sql.d.ts +8 -0
  21. package/build/services/mcp/sql.js +24 -1
  22. package/build/services/role/seed.d.ts +21 -0
  23. package/build/services/role/seed.js +113 -0
  24. package/build/services/role/sql.d.ts +8 -0
  25. package/build/services/role/sql.js +24 -1
  26. package/build/services/role/types.d.ts +7 -3
  27. package/build/services/role/types.js +7 -3
  28. package/build/services/topics/index.d.ts +2 -0
  29. package/build/services/topics/index.js +7 -0
  30. package/build/services/topics/sql.d.ts +2 -0
  31. package/build/services/topics/sql.js +5 -1
  32. package/build/services/topics/system-topics.d.ts +3 -2
  33. package/build/services/topics/system-topics.js +4 -3
  34. package/build/services/user/index.d.ts +1 -1
  35. package/build/services/user/index.js +2 -1
  36. package/build/services/user/roles.d.ts +8 -0
  37. package/build/services/user/roles.js +29 -0
  38. package/build/services/user/sql.d.ts +6 -0
  39. package/build/services/user/sql.js +9 -1
  40. package/build/services/user/sso-provision.js +14 -3
  41. package/build/start/apply-report.d.ts +26 -0
  42. package/build/start/apply-report.js +65 -0
  43. package/build/start/config.js +8 -1
  44. package/build/start/workers.js +170 -33
  45. package/build/tsconfig.tsbuildinfo +1 -1
  46. package/build/types/auth.d.ts +27 -1
  47. package/build/types/index.d.ts +1 -1
  48. package/build/types/startup.d.ts +108 -4
  49. package/dashboard/dist/assets/{AdminDashboard-CahUELrr.js → AdminDashboard-D2unnS2b.js} +2 -2
  50. package/dashboard/dist/assets/{AdminDashboard-CahUELrr.js.map → AdminDashboard-D2unnS2b.js.map} +1 -1
  51. package/dashboard/dist/assets/{AgentConfigPage-CkMv-cTV.js → AgentConfigPage-CBkf403g.js} +2 -2
  52. package/dashboard/dist/assets/{AgentConfigPage-CkMv-cTV.js.map → AgentConfigPage-CBkf403g.js.map} +1 -1
  53. package/dashboard/dist/assets/{AgentDetailPage-ZGea3Z4A.js → AgentDetailPage-CAIU3yzo.js} +2 -2
  54. package/dashboard/dist/assets/{AgentDetailPage-ZGea3Z4A.js.map → AgentDetailPage-CAIU3yzo.js.map} +1 -1
  55. package/dashboard/dist/assets/{AgentsPage-BMWSUJGi.js → AgentsPage-qP9fWMyT.js} +2 -2
  56. package/dashboard/dist/assets/{AgentsPage-BMWSUJGi.js.map → AgentsPage-qP9fWMyT.js.map} +1 -1
  57. package/dashboard/dist/assets/AvailableEscalationsPage-DvWaiafI.js +2 -0
  58. package/dashboard/dist/assets/AvailableEscalationsPage-DvWaiafI.js.map +1 -0
  59. package/dashboard/dist/assets/{BotPicker-DN8tq7pz.js → BotPicker-BN-MKITV.js} +2 -2
  60. package/dashboard/dist/assets/{BotPicker-DN8tq7pz.js.map → BotPicker-BN-MKITV.js.map} +1 -1
  61. package/dashboard/dist/assets/{CapabilitiesPage-EAO3Ae0S.js → CapabilitiesPage-CJfmWokN.js} +2 -2
  62. package/dashboard/dist/assets/{CapabilitiesPage-EAO3Ae0S.js.map → CapabilitiesPage-CJfmWokN.js.map} +1 -1
  63. package/dashboard/dist/assets/{CollapsibleSection-J-RejbjG.js → CollapsibleSection-BvMHlHyt.js} +2 -2
  64. package/dashboard/dist/assets/{CollapsibleSection-J-RejbjG.js.map → CollapsibleSection-BvMHlHyt.js.map} +1 -1
  65. package/dashboard/dist/assets/{CountdownTimer-Dw8N9tmQ.js → CountdownTimer-By4rbMDN.js} +2 -2
  66. package/dashboard/dist/assets/{CountdownTimer-Dw8N9tmQ.js.map → CountdownTimer-By4rbMDN.js.map} +1 -1
  67. package/dashboard/dist/assets/{CredentialsPage-CTurxwRn.js → CredentialsPage-2TvvRwGJ.js} +2 -2
  68. package/dashboard/dist/assets/{CredentialsPage-CTurxwRn.js.map → CredentialsPage-2TvvRwGJ.js.map} +1 -1
  69. package/dashboard/dist/assets/{CronLabel-DZ5TmIQG.js → CronLabel-HxmsDSnV.js} +2 -2
  70. package/dashboard/dist/assets/{CronLabel-DZ5TmIQG.js.map → CronLabel-HxmsDSnV.js.map} +1 -1
  71. package/dashboard/dist/assets/{CustomDurationPicker-BES_FTrm.js → CustomDurationPicker-8Ao-1pKA.js} +2 -2
  72. package/dashboard/dist/assets/{CustomDurationPicker-BES_FTrm.js.map → CustomDurationPicker-8Ao-1pKA.js.map} +1 -1
  73. package/dashboard/dist/assets/{DropZone-BWD9aGOz.js → DropZone-DovkhN2p.js} +2 -2
  74. package/dashboard/dist/assets/{DropZone-BWD9aGOz.js.map → DropZone-DovkhN2p.js.map} +1 -1
  75. package/dashboard/dist/assets/{ElapsedCell-CIGRporU.js → ElapsedCell-BkRu9Xpd.js} +2 -2
  76. package/dashboard/dist/assets/{ElapsedCell-CIGRporU.js.map → ElapsedCell-BkRu9Xpd.js.map} +1 -1
  77. package/dashboard/dist/assets/EntityTimelinePanel--E6hLMCM.js +2 -0
  78. package/dashboard/dist/assets/{EntityTimelinePanel-CKIFsDOB.js.map → EntityTimelinePanel--E6hLMCM.js.map} +1 -1
  79. package/dashboard/dist/assets/{EscalationListSchemaPage-DOrv-quD.js → EscalationListSchemaPage-CdFtqEEX.js} +3 -3
  80. package/dashboard/dist/assets/{EscalationListSchemaPage-DOrv-quD.js.map → EscalationListSchemaPage-CdFtqEEX.js.map} +1 -1
  81. package/dashboard/dist/assets/{EscalationSchemaPage-DAOVAxAM.js → EscalationSchemaPage-BsjS2axz.js} +2 -2
  82. package/dashboard/dist/assets/{EscalationSchemaPage-DAOVAxAM.js.map → EscalationSchemaPage-BsjS2axz.js.map} +1 -1
  83. package/dashboard/dist/assets/{EscalationsOverview-B27sDn5O.js → EscalationsOverview-FL-xteVc.js} +2 -2
  84. package/dashboard/dist/assets/{EscalationsOverview-B27sDn5O.js.map → EscalationsOverview-FL-xteVc.js.map} +1 -1
  85. package/dashboard/dist/assets/{EventTable-Co1d40Qq.js → EventTable-BOcgZ78i.js} +2 -2
  86. package/dashboard/dist/assets/{EventTable-Co1d40Qq.js.map → EventTable-BOcgZ78i.js.map} +1 -1
  87. package/dashboard/dist/assets/{FilterBar-2ZfU21EA.js → FilterBar-DfCiCmm-.js} +2 -2
  88. package/dashboard/dist/assets/{FilterBar-2ZfU21EA.js.map → FilterBar-DfCiCmm-.js.map} +1 -1
  89. package/dashboard/dist/assets/{GraphInvokePage-pdvc9VSv.js → GraphInvokePage-DfVeiN-8.js} +2 -2
  90. package/dashboard/dist/assets/{GraphInvokePage-pdvc9VSv.js.map → GraphInvokePage-DfVeiN-8.js.map} +1 -1
  91. package/dashboard/dist/assets/{HomePage-CLlDsBnw.js → HomePage-C2IhF9WB.js} +2 -2
  92. package/dashboard/dist/assets/{HomePage-CLlDsBnw.js.map → HomePage-C2IhF9WB.js.map} +1 -1
  93. package/dashboard/dist/assets/{ListToolbar-DMremj2e.js → ListToolbar-DA0PTOl5.js} +2 -2
  94. package/dashboard/dist/assets/{ListToolbar-DMremj2e.js.map → ListToolbar-DA0PTOl5.js.map} +1 -1
  95. package/dashboard/dist/assets/{McpOverview-BhztmhoL.js → McpOverview-DstPch5M.js} +2 -2
  96. package/dashboard/dist/assets/{McpOverview-BhztmhoL.js.map → McpOverview-DstPch5M.js.map} +1 -1
  97. package/dashboard/dist/assets/{McpQueryDetailPage-BseZfrhc.js → McpQueryDetailPage-Csv0PKE0.js} +2 -2
  98. package/dashboard/dist/assets/{McpQueryDetailPage-BseZfrhc.js.map → McpQueryDetailPage-Csv0PKE0.js.map} +1 -1
  99. package/dashboard/dist/assets/{McpQueryPage-DMzkble7.js → McpQueryPage-DxmqCkly.js} +2 -2
  100. package/dashboard/dist/assets/{McpQueryPage-DMzkble7.js.map → McpQueryPage-DxmqCkly.js.map} +1 -1
  101. package/dashboard/dist/assets/{McpRunDetailPage-DqaBQAnn.js → McpRunDetailPage-CcJOlp_B.js} +2 -2
  102. package/dashboard/dist/assets/{McpRunDetailPage-DqaBQAnn.js.map → McpRunDetailPage-CcJOlp_B.js.map} +1 -1
  103. package/dashboard/dist/assets/{McpRunsPage-Du8GnHFv.js → McpRunsPage-BZCR043j.js} +2 -2
  104. package/dashboard/dist/assets/{McpRunsPage-Du8GnHFv.js.map → McpRunsPage-BZCR043j.js.map} +1 -1
  105. package/dashboard/dist/assets/{NamespacePill-C09QuOEz.js → NamespacePill-BeifzjUm.js} +2 -2
  106. package/dashboard/dist/assets/{NamespacePill-C09QuOEz.js.map → NamespacePill-BeifzjUm.js.map} +1 -1
  107. package/dashboard/dist/assets/{OperationsPage-CL2feqwS.js → OperationsPage-DMsPXITh.js} +3 -3
  108. package/dashboard/dist/assets/{OperationsPage-CL2feqwS.js.map → OperationsPage-DMsPXITh.js.map} +1 -1
  109. package/dashboard/dist/assets/{OperatorDashboard-BHiGQZN_.js → OperatorDashboard-DSloftQ5.js} +2 -2
  110. package/dashboard/dist/assets/{OperatorDashboard-BHiGQZN_.js.map → OperatorDashboard-DSloftQ5.js.map} +1 -1
  111. package/dashboard/dist/assets/{PageHeader-B7YJxa4o.js → PageHeader-BpeBHqyc.js} +2 -2
  112. package/dashboard/dist/assets/{PageHeader-B7YJxa4o.js.map → PageHeader-BpeBHqyc.js.map} +1 -1
  113. package/dashboard/dist/assets/{PageHeaderWithStats-gT-5ovJh.js → PageHeaderWithStats-JXeUFa_g.js} +2 -2
  114. package/dashboard/dist/assets/{PageHeaderWithStats-gT-5ovJh.js.map → PageHeaderWithStats-JXeUFa_g.js.map} +1 -1
  115. package/dashboard/dist/assets/{PersonaDetailPage-D6GGZgUS.js → PersonaDetailPage-jDVRCLbU.js} +2 -2
  116. package/dashboard/dist/assets/{PersonaDetailPage-D6GGZgUS.js.map → PersonaDetailPage-jDVRCLbU.js.map} +1 -1
  117. package/dashboard/dist/assets/{PersonasPage-BQYCdmAs.js → PersonasPage-DnDDwXoy.js} +2 -2
  118. package/dashboard/dist/assets/{PersonasPage-BQYCdmAs.js.map → PersonasPage-DnDDwXoy.js.map} +1 -1
  119. package/dashboard/dist/assets/{ProcessDetailPage-P45jOLim.js → ProcessDetailPage-CfAUU6q2.js} +2 -2
  120. package/dashboard/dist/assets/{ProcessDetailPage-P45jOLim.js.map → ProcessDetailPage-CfAUU6q2.js.map} +1 -1
  121. package/dashboard/dist/assets/{ProcessesListPage-CgdzgCY6.js → ProcessesListPage-CnAXzaVv.js} +2 -2
  122. package/dashboard/dist/assets/{ProcessesListPage-CgdzgCY6.js.map → ProcessesListPage-CnAXzaVv.js.map} +1 -1
  123. package/dashboard/dist/assets/RoleDetailPage-Dg1p6Qz_.js +8 -0
  124. package/dashboard/dist/assets/RoleDetailPage-Dg1p6Qz_.js.map +1 -0
  125. package/dashboard/dist/assets/{RolePill-CtYI1_k1.js → RolePill-EocCXZh0.js} +2 -2
  126. package/dashboard/dist/assets/{RolePill-CtYI1_k1.js.map → RolePill-EocCXZh0.js.map} +1 -1
  127. package/dashboard/dist/assets/{RolesPage-Ci7_PbPt.js → RolesPage-DeRVQux8.js} +2 -2
  128. package/dashboard/dist/assets/{RolesPage-Ci7_PbPt.js.map → RolesPage-DeRVQux8.js.map} +1 -1
  129. package/dashboard/dist/assets/{RunAsSelector-CB0pV097.js → RunAsSelector-BFNqQGuG.js} +2 -2
  130. package/dashboard/dist/assets/{RunAsSelector-CB0pV097.js.map → RunAsSelector-BFNqQGuG.js.map} +1 -1
  131. package/dashboard/dist/assets/{ScanCodesPage-DpbPhAj1.js → ScanCodesPage-D0GOdk5m.js} +2 -2
  132. package/dashboard/dist/assets/{ScanCodesPage-DpbPhAj1.js.map → ScanCodesPage-D0GOdk5m.js.map} +1 -1
  133. package/dashboard/dist/assets/{ScanSchemeDetailPage-CGnMGlt0.js → ScanSchemeDetailPage-vFqoe_P4.js} +2 -2
  134. package/dashboard/dist/assets/{ScanSchemeDetailPage-CGnMGlt0.js.map → ScanSchemeDetailPage-vFqoe_P4.js.map} +1 -1
  135. package/dashboard/dist/assets/ScanStationPage-DH1ynTX9.js +2 -0
  136. package/dashboard/dist/assets/{ScanStationPage-BTeV7TGy.js.map → ScanStationPage-DH1ynTX9.js.map} +1 -1
  137. package/dashboard/dist/assets/{ScopeBadge-B76rOtA0.js → ScopeBadge-H5dEqeHi.js} +2 -2
  138. package/dashboard/dist/assets/{ScopeBadge-B76rOtA0.js.map → ScopeBadge-H5dEqeHi.js.map} +1 -1
  139. package/dashboard/dist/assets/{StickyPagination-DH-0JEyx.js → StickyPagination-B99hVu95.js} +2 -2
  140. package/dashboard/dist/assets/{StickyPagination-DH-0JEyx.js.map → StickyPagination-B99hVu95.js.map} +1 -1
  141. package/dashboard/dist/assets/{StreamMessageDetail-u-pDrHfS.js → StreamMessageDetail-DcDGISrv.js} +2 -2
  142. package/dashboard/dist/assets/{StreamMessageDetail-u-pDrHfS.js.map → StreamMessageDetail-DcDGISrv.js.map} +1 -1
  143. package/dashboard/dist/assets/{SwimlaneTimeline-vN1N8BGy.js → SwimlaneTimeline-BP3mSQAg.js} +2 -2
  144. package/dashboard/dist/assets/{SwimlaneTimeline-vN1N8BGy.js.map → SwimlaneTimeline-BP3mSQAg.js.map} +1 -1
  145. package/dashboard/dist/assets/{TagInput-BQAPOp17.js → TagInput-Ch5u6iPH.js} +2 -2
  146. package/dashboard/dist/assets/{TagInput-BQAPOp17.js.map → TagInput-Ch5u6iPH.js.map} +1 -1
  147. package/dashboard/dist/assets/{TaskDetailPage-CAQm6avs.js → TaskDetailPage-_XWLJzt-.js} +2 -2
  148. package/dashboard/dist/assets/{TaskDetailPage-CAQm6avs.js.map → TaskDetailPage-_XWLJzt-.js.map} +1 -1
  149. package/dashboard/dist/assets/{TaskQueuePill-C2NImrwl.js → TaskQueuePill-Bz146p-P.js} +2 -2
  150. package/dashboard/dist/assets/{TaskQueuePill-C2NImrwl.js.map → TaskQueuePill-Bz146p-P.js.map} +1 -1
  151. package/dashboard/dist/assets/{TasksListPage-C1bT3DT9.js → TasksListPage-iGriKiO2.js} +2 -2
  152. package/dashboard/dist/assets/{TasksListPage-C1bT3DT9.js.map → TasksListPage-iGriKiO2.js.map} +1 -1
  153. package/dashboard/dist/assets/{TimeAgo-qZxkJ8Am.js → TimeAgo-BO33VeFl.js} +2 -2
  154. package/dashboard/dist/assets/{TimeAgo-qZxkJ8Am.js.map → TimeAgo-BO33VeFl.js.map} +1 -1
  155. package/dashboard/dist/assets/{TimestampCell-CC3dz6FG.js → TimestampCell-KAjX7w35.js} +2 -2
  156. package/dashboard/dist/assets/{TimestampCell-CC3dz6FG.js.map → TimestampCell-KAjX7w35.js.map} +1 -1
  157. package/dashboard/dist/assets/{ToolPill-FOZm67TS.js → ToolPill-sj8OkBB0.js} +2 -2
  158. package/dashboard/dist/assets/{ToolPill-FOZm67TS.js.map → ToolPill-sj8OkBB0.js.map} +1 -1
  159. package/dashboard/dist/assets/{ToolTestPanel-C3sSQUvw.js → ToolTestPanel-BchG2yC6.js} +2 -2
  160. package/dashboard/dist/assets/{ToolTestPanel-C3sSQUvw.js.map → ToolTestPanel-BchG2yC6.js.map} +1 -1
  161. package/dashboard/dist/assets/{TopicDetailPage-ClI0I8Mu.js → TopicDetailPage-BaK8H0o1.js} +2 -2
  162. package/dashboard/dist/assets/{TopicDetailPage-ClI0I8Mu.js.map → TopicDetailPage-BaK8H0o1.js.map} +1 -1
  163. package/dashboard/dist/assets/{TopicsPage-CNf2VZud.js → TopicsPage-DEyvm669.js} +2 -2
  164. package/dashboard/dist/assets/{TopicsPage-CNf2VZud.js.map → TopicsPage-DEyvm669.js.map} +1 -1
  165. package/dashboard/dist/assets/{UserName-BdMHmyL3.js → UserName-BV6SKI9P.js} +2 -2
  166. package/dashboard/dist/assets/{UserName-BdMHmyL3.js.map → UserName-BV6SKI9P.js.map} +1 -1
  167. package/dashboard/dist/assets/{WorkflowExecutionPage-Ba4mYFoW.js → WorkflowExecutionPage-1sKoEFNC.js} +2 -2
  168. package/dashboard/dist/assets/{WorkflowExecutionPage-Ba4mYFoW.js.map → WorkflowExecutionPage-1sKoEFNC.js.map} +1 -1
  169. package/dashboard/dist/assets/{WorkflowPill-C5TEWlfO.js → WorkflowPill-BcA7S3gI.js} +2 -2
  170. package/dashboard/dist/assets/{WorkflowPill-C5TEWlfO.js.map → WorkflowPill-BcA7S3gI.js.map} +1 -1
  171. package/dashboard/dist/assets/{WorkflowsDashboard-ImEm16tX.js → WorkflowsDashboard-YTHDZZOD.js} +2 -2
  172. package/dashboard/dist/assets/{WorkflowsDashboard-ImEm16tX.js.map → WorkflowsDashboard-YTHDZZOD.js.map} +1 -1
  173. package/dashboard/dist/assets/{WorkflowsOverview-BpWyV_tj.js → WorkflowsOverview-ucXPLTtm.js} +2 -2
  174. package/dashboard/dist/assets/{WorkflowsOverview-BpWyV_tj.js.map → WorkflowsOverview-ucXPLTtm.js.map} +1 -1
  175. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BFBlw1YY.js → YamlWorkflowDetailPage-C_RTZKqZ.js} +2 -2
  176. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BFBlw1YY.js.map → YamlWorkflowDetailPage-C_RTZKqZ.js.map} +1 -1
  177. package/dashboard/dist/assets/{YamlWorkflowsPage-C9gxapRg.js → YamlWorkflowsPage-Usid3w2W.js} +2 -2
  178. package/dashboard/dist/assets/{YamlWorkflowsPage-C9gxapRg.js.map → YamlWorkflowsPage-Usid3w2W.js.map} +1 -1
  179. package/dashboard/dist/assets/{agents-BJQtJTiz.js → agents-BTDsRK8e.js} +2 -2
  180. package/dashboard/dist/assets/{agents-BJQtJTiz.js.map → agents-BTDsRK8e.js.map} +1 -1
  181. package/dashboard/dist/assets/{bots-DDlU1gMy.js → bots-DxLrfdfr.js} +2 -2
  182. package/dashboard/dist/assets/{bots-DDlU1gMy.js.map → bots-DxLrfdfr.js.map} +1 -1
  183. package/dashboard/dist/assets/{capabilities-qYrUpdid.js → capabilities-DbTmeW-S.js} +2 -2
  184. package/dashboard/dist/assets/{capabilities-qYrUpdid.js.map → capabilities-DbTmeW-S.js.map} +1 -1
  185. package/dashboard/dist/assets/{controlplane-DnJ1VDTs.js → controlplane-CEBntAL4.js} +2 -2
  186. package/dashboard/dist/assets/{controlplane-DnJ1VDTs.js.map → controlplane-CEBntAL4.js.map} +1 -1
  187. package/dashboard/dist/assets/escalation-columns-Carqdwoh.js +2 -0
  188. package/dashboard/dist/assets/escalation-columns-Carqdwoh.js.map +1 -0
  189. package/dashboard/dist/assets/{index-KotwPxGB.js → index-B2hz0pUh.js} +2 -2
  190. package/dashboard/dist/assets/{index-KotwPxGB.js.map → index-B2hz0pUh.js.map} +1 -1
  191. package/dashboard/dist/assets/{index-04QQsxMp.js → index-B9m7fA3M.js} +2 -2
  192. package/dashboard/dist/assets/{index-04QQsxMp.js.map → index-B9m7fA3M.js.map} +1 -1
  193. package/dashboard/dist/assets/{index-B5q8ksfv.js → index-BhapT-Vl.js} +2 -2
  194. package/dashboard/dist/assets/{index-B5q8ksfv.js.map → index-BhapT-Vl.js.map} +1 -1
  195. package/dashboard/dist/assets/{index-CtAPxA03.js → index-Bqm4zu46.js} +2 -2
  196. package/dashboard/dist/assets/{index-CtAPxA03.js.map → index-Bqm4zu46.js.map} +1 -1
  197. package/dashboard/dist/assets/{index-LiLVAQzB.js → index-C0hSttF5.js} +2 -2
  198. package/dashboard/dist/assets/{index-LiLVAQzB.js.map → index-C0hSttF5.js.map} +1 -1
  199. package/dashboard/dist/assets/{index-DhRdWzz0.js → index-CDkg6iOE.js} +4 -4
  200. package/dashboard/dist/assets/{index-DhRdWzz0.js.map → index-CDkg6iOE.js.map} +1 -1
  201. package/dashboard/dist/assets/{index-D-5F3Bw-.js → index-CVBBkW8V.js} +2 -2
  202. package/dashboard/dist/assets/{index-D-5F3Bw-.js.map → index-CVBBkW8V.js.map} +1 -1
  203. package/dashboard/dist/assets/{index-BUpB9ezn.js → index-CgIBjZta.js} +2 -2
  204. package/dashboard/dist/assets/{index-BUpB9ezn.js.map → index-CgIBjZta.js.map} +1 -1
  205. package/dashboard/dist/assets/{index-C991QfUN.js → index-DBXm49S6.js} +27 -27
  206. package/dashboard/dist/assets/index-DBXm49S6.js.map +1 -0
  207. package/dashboard/dist/assets/{index-BAqQSgxn.js → index-De8iV1y2.js} +2 -2
  208. package/dashboard/dist/assets/{index-BAqQSgxn.js.map → index-De8iV1y2.js.map} +1 -1
  209. package/dashboard/dist/assets/{index-BHbO4F8Z.js → index-DsgjFRvA.js} +2 -2
  210. package/dashboard/dist/assets/{index-BHbO4F8Z.js.map → index-DsgjFRvA.js.map} +1 -1
  211. package/dashboard/dist/assets/{index-CFlmubaP.js → index-kes8V4Xw.js} +2 -2
  212. package/dashboard/dist/assets/{index-CFlmubaP.js.map → index-kes8V4Xw.js.map} +1 -1
  213. package/dashboard/dist/assets/{index-CroJTX8g.js → index-nwLh3CK2.js} +2 -2
  214. package/dashboard/dist/assets/{index-CroJTX8g.js.map → index-nwLh3CK2.js.map} +1 -1
  215. package/dashboard/dist/assets/{knowledge-CnMeg8Rw.js → knowledge-oFYAanUp.js} +2 -2
  216. package/dashboard/dist/assets/{knowledge-CnMeg8Rw.js.map → knowledge-oFYAanUp.js.map} +1 -1
  217. package/dashboard/dist/assets/{mcp-CHPSFv7m.js → mcp-C9Kw4Ow8.js} +2 -2
  218. package/dashboard/dist/assets/{mcp-CHPSFv7m.js.map → mcp-C9Kw4Ow8.js.map} +1 -1
  219. package/dashboard/dist/assets/{mcp-query-C19EGwdN.js → mcp-query-BwzpZ91Z.js} +2 -2
  220. package/dashboard/dist/assets/{mcp-query-C19EGwdN.js.map → mcp-query-BwzpZ91Z.js.map} +1 -1
  221. package/dashboard/dist/assets/{personas-Br1Vo2S3.js → personas-D5ehmtRb.js} +2 -2
  222. package/dashboard/dist/assets/{personas-Br1Vo2S3.js.map → personas-D5ehmtRb.js.map} +1 -1
  223. package/dashboard/dist/assets/{pipelines-QMrFoJZX.js → pipelines-xgPKGEdD.js} +2 -2
  224. package/dashboard/dist/assets/{pipelines-QMrFoJZX.js.map → pipelines-xgPKGEdD.js.map} +1 -1
  225. package/dashboard/dist/assets/{tasks-Doy_7cGX.js → tasks-CLyrl-MU.js} +2 -2
  226. package/dashboard/dist/assets/{tasks-Doy_7cGX.js.map → tasks-CLyrl-MU.js.map} +1 -1
  227. package/dashboard/dist/assets/{topics-BcJS4G09.js → topics-B_gO7CQ0.js} +2 -2
  228. package/dashboard/dist/assets/{topics-BcJS4G09.js.map → topics-B_gO7CQ0.js.map} +1 -1
  229. package/dashboard/dist/assets/{useEventHooks-ldE5o0ok.js → useEventHooks-BdWYm63U.js} +2 -2
  230. package/dashboard/dist/assets/{useEventHooks-ldE5o0ok.js.map → useEventHooks-BdWYm63U.js.map} +1 -1
  231. package/dashboard/dist/assets/{useNamespace-CCu8fZc6.js → useNamespace-C238M4Qn.js} +2 -2
  232. package/dashboard/dist/assets/{useNamespace-CCu8fZc6.js.map → useNamespace-C238M4Qn.js.map} +1 -1
  233. package/dashboard/dist/assets/{useYamlActivityEvents-DDrXwFXX.js → useYamlActivityEvents-B2ryEK4S.js} +2 -2
  234. package/dashboard/dist/assets/{useYamlActivityEvents-DDrXwFXX.js.map → useYamlActivityEvents-B2ryEK4S.js.map} +1 -1
  235. package/dashboard/dist/assets/{users-BvKhSq1i.js → users-BFaTe6Mt.js} +2 -2
  236. package/dashboard/dist/assets/{users-BvKhSq1i.js.map → users-BFaTe6Mt.js.map} +1 -1
  237. package/dashboard/dist/assets/{vendor-icons-Aw7bzBxa.js → vendor-icons-BPsNpK3Y.js} +142 -137
  238. package/dashboard/dist/assets/vendor-icons-BPsNpK3Y.js.map +1 -0
  239. package/dashboard/dist/assets/{workflows-D09ysQF1.js → workflows-DXz7tZxp.js} +2 -2
  240. package/dashboard/dist/assets/{workflows-D09ysQF1.js.map → workflows-DXz7tZxp.js.map} +1 -1
  241. package/dashboard/dist/assets/{yaml-workflows-BFHrxEl4.js → yaml-workflows-u3xuRAnS.js} +2 -2
  242. package/dashboard/dist/assets/{yaml-workflows-BFHrxEl4.js.map → yaml-workflows-u3xuRAnS.js.map} +1 -1
  243. package/dashboard/dist/index.html +2 -2
  244. package/docs/code-owned-configuration.md +131 -0
  245. package/docs/hitl/x-lt-transition.md +13 -0
  246. package/docs/scan-codes.md +24 -0
  247. package/package.json +1 -1
  248. package/dashboard/dist/assets/AvailableEscalationsPage-BjsbfsWW.js +0 -2
  249. package/dashboard/dist/assets/AvailableEscalationsPage-BjsbfsWW.js.map +0 -1
  250. package/dashboard/dist/assets/EntityTimelinePanel-CKIFsDOB.js +0 -2
  251. package/dashboard/dist/assets/RoleDetailPage-Dme2pa9z.js +0 -8
  252. package/dashboard/dist/assets/RoleDetailPage-Dme2pa9z.js.map +0 -1
  253. package/dashboard/dist/assets/ScanStationPage-BTeV7TGy.js +0 -2
  254. package/dashboard/dist/assets/escalation-columns-BCnVWr_n.js +0 -2
  255. package/dashboard/dist/assets/escalation-columns-BCnVWr_n.js.map +0 -1
  256. package/dashboard/dist/assets/index-C991QfUN.js.map +0 -1
  257. package/dashboard/dist/assets/vendor-icons-Aw7bzBxa.js.map +0 -1
@@ -0,0 +1,21 @@
1
+ import type { LTRoleConfig } from '../../types/startup';
2
+ export type RoleApplyOutcome = 'applied' | 'unchanged' | 'db-owned';
3
+ /** Recorded on schema snapshots written by the startup apply. */
4
+ export declare const STARTUP_CHANGE_SUMMARY = "startup apply (code)";
5
+ /**
6
+ * Roles carrying real configuration (title or form schema) — the candidate
7
+ * set for the startup orphan report. Bare FK-ensured rows are excluded.
8
+ */
9
+ export declare function listConfiguredRoles(): Promise<string[]>;
10
+ /**
11
+ * Register one declared role at startup.
12
+ *
13
+ * The role is created if missing. Under code ownership its declared fields
14
+ * are compared against the stored row and written through the versioned
15
+ * PATCH path when they differ — a changed form/metadata schema advances the
16
+ * role's schema version and snapshots the pair; an unchanged declaration is
17
+ * a no-op. Under db ownership the metadata is written exactly once (at
18
+ * creation); afterwards the DB owns the row and drift is warn-logged only.
19
+ * Escalation targets use replace semantics when declared.
20
+ */
21
+ export declare function applyRoleConfig(cfg: LTRoleConfig, codeOwned: boolean): Promise<RoleApplyOutcome>;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STARTUP_CHANGE_SUMMARY = void 0;
4
+ exports.listConfiguredRoles = listConfiguredRoles;
5
+ exports.applyRoleConfig = applyRoleConfig;
6
+ const node_util_1 = require("node:util");
7
+ const db_1 = require("../../lib/db");
8
+ const logger_1 = require("../../lib/logger");
9
+ const index_1 = require("./index");
10
+ const sql_1 = require("./sql");
11
+ /** Recorded on schema snapshots written by the startup apply. */
12
+ exports.STARTUP_CHANGE_SUMMARY = 'startup apply (code)';
13
+ /** Metadata fields shared 1:1 between LTRoleConfig and UpdateRoleInput. */
14
+ const METADATA_FIELDS = [
15
+ 'title', 'description', 'form_schema', 'metadata_schema', 'list_schema',
16
+ 'properties', 'ops_visible', 'ops_home_default', 'parent_role',
17
+ 'upstream_roles', 'default_pins', 'enforce_schema', 'sla_minutes',
18
+ 'target_per_hour', 'worker_count', 'priority_threshold_minutes',
19
+ 'priority_facet', 'entity_facet', 'entity_state_source',
20
+ ];
21
+ /**
22
+ * Compare one declared field against the stored row. jsonb round-trips lose
23
+ * key order, so structured values must be compared as parsed values — never
24
+ * by string. Arrays compare order-insensitively (sorted).
25
+ */
26
+ function fieldDiffers(field, declared, row) {
27
+ const stored = row[field];
28
+ if (Array.isArray(declared)) {
29
+ const a = [...declared].sort();
30
+ const b = Array.isArray(stored) ? [...stored].sort() : [];
31
+ return !(0, node_util_1.isDeepStrictEqual)(a, b);
32
+ }
33
+ if (declared !== null && typeof declared === 'object') {
34
+ return !(0, node_util_1.isDeepStrictEqual)(declared, stored ?? null);
35
+ }
36
+ // Numeric columns round-trip as strings from the driver — compare as numbers
37
+ // or every declared dial would read as changed on every boot.
38
+ if (typeof declared === 'number') {
39
+ return stored == null || Number(stored) !== declared;
40
+ }
41
+ return declared !== (stored ?? undefined);
42
+ }
43
+ /** The declared fields that differ from the stored row (PATCH-shaped). */
44
+ function diffDeclaredFields(cfg, row) {
45
+ const changed = {};
46
+ for (const field of METADATA_FIELDS) {
47
+ const declared = cfg[field];
48
+ if (declared === undefined)
49
+ continue;
50
+ if (fieldDiffers(field, declared, row)) {
51
+ changed[field] = declared;
52
+ }
53
+ }
54
+ return changed;
55
+ }
56
+ /**
57
+ * Roles carrying real configuration (title or form schema) — the candidate
58
+ * set for the startup orphan report. Bare FK-ensured rows are excluded.
59
+ */
60
+ async function listConfiguredRoles() {
61
+ const pool = (0, db_1.getPool)();
62
+ const { rows } = await pool.query(sql_1.LIST_CONFIGURED_ROLES);
63
+ return rows.map((r) => r.role);
64
+ }
65
+ /**
66
+ * Register one declared role at startup.
67
+ *
68
+ * The role is created if missing. Under code ownership its declared fields
69
+ * are compared against the stored row and written through the versioned
70
+ * PATCH path when they differ — a changed form/metadata schema advances the
71
+ * role's schema version and snapshots the pair; an unchanged declaration is
72
+ * a no-op. Under db ownership the metadata is written exactly once (at
73
+ * creation); afterwards the DB owns the row and drift is warn-logged only.
74
+ * Escalation targets use replace semantics when declared.
75
+ */
76
+ async function applyRoleConfig(cfg, codeOwned) {
77
+ const created = await (0, index_1.createRole)(cfg.role);
78
+ const pool = (0, db_1.getPool)();
79
+ const { rows } = await pool.query(sql_1.GET_ROLE_ROW, [cfg.role]);
80
+ const row = rows[0];
81
+ const changed = diffDeclaredFields(cfg, row);
82
+ const changedFields = Object.keys(changed);
83
+ const declaredTargets = cfg.escalation_targets != null ? [...cfg.escalation_targets].sort() : null;
84
+ const storedTargets = declaredTargets != null ? await (0, index_1.getEscalationTargets)(cfg.role) : null;
85
+ const targetsDiffer = declaredTargets != null && !(0, node_util_1.isDeepStrictEqual)(declaredTargets, [...storedTargets].sort());
86
+ if (!codeOwned) {
87
+ if (created) {
88
+ // First boot for this role — write the declared metadata once. From
89
+ // here the DB owns the row (the dependents' "only if unconfigured"
90
+ // guard, moved into core).
91
+ if (changedFields.length > 0) {
92
+ await (0, index_1.updateRoleMetadata)(cfg.role, changed);
93
+ }
94
+ if (targetsDiffer)
95
+ await (0, index_1.replaceEscalationTargets)(cfg.role, declaredTargets);
96
+ return 'applied';
97
+ }
98
+ if (changedFields.length > 0 || targetsDiffer) {
99
+ const drifts = [...changedFields, ...(targetsDiffer ? ['escalation_targets'] : [])];
100
+ logger_1.loggerRegistry.warn(`[long-tail] config drift: role ${cfg.role} — ${drifts.join(', ')} differ between code and DB`);
101
+ }
102
+ return 'db-owned';
103
+ }
104
+ if (changedFields.length === 0 && !targetsDiffer)
105
+ return 'unchanged';
106
+ if (changedFields.length > 0) {
107
+ await (0, index_1.updateRoleMetadata)(cfg.role, { ...changed, change_summary: exports.STARTUP_CHANGE_SUMMARY });
108
+ }
109
+ if (targetsDiffer) {
110
+ await (0, index_1.replaceEscalationTargets)(cfg.role, declaredTargets);
111
+ }
112
+ return 'applied';
113
+ }
@@ -14,6 +14,14 @@ export declare const DELETE_ESCALATION_CHAINS_BY_SOURCE = "\n DELETE FROM lt_co
14
14
  export declare const CHECK_ESCALATION_CHAIN_EXISTS = "\n SELECT 1\n FROM lt_config_role_escalations\n WHERE source_role = $1 AND target_role = $2";
15
15
  export declare const ENSURE_ROLE_EXISTS = "\n INSERT INTO lt_roles (role) VALUES ($1)\n ON CONFLICT DO NOTHING";
16
16
  export declare const LIST_ROLES = "\n SELECT role FROM lt_roles ORDER BY role";
17
+ /**
18
+ * Roles carrying real configuration (a title or a form schema). Bare FK rows
19
+ * and membership-only roles are excluded — the startup orphan report uses
20
+ * this so auto-ensured rows never read as drift.
21
+ */
22
+ export declare const LIST_CONFIGURED_ROLES = "\n SELECT role FROM lt_roles\n WHERE title IS NOT NULL OR form_schema IS NOT NULL\n ORDER BY role";
23
+ /** One role's full metadata row (plus upstream inputs) for startup diffing. */
24
+ export declare const GET_ROLE_ROW = "\n SELECT\n r.role, r.title, r.description, r.form_schema, r.metadata_schema,\n r.properties, r.ops_visible, r.ops_home_default, r.parent_role,\n r.sla_minutes, r.target_per_hour, r.worker_count,\n r.priority_threshold_minutes, r.priority_facet,\n r.entity_facet, r.entity_state_source,\n r.list_schema, r.default_pins, r.enforce_schema,\n r.current_schema_version, r.current_list_schema_version,\n COALESCE((SELECT array_agg(u.upstream_role ORDER BY u.upstream_role)\n FROM lt_role_upstreams u WHERE u.role = r.role), '{}') AS upstream_roles\n FROM lt_roles r\n WHERE r.role = $1";
17
25
  export declare const DELETE_ROLE = "\n DELETE FROM lt_roles WHERE role = $1";
18
26
  /**
19
27
  * PATCH semantics in ONE atomic statement: each column has a boolean
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // ─── Escalation chain queries ───────────────────────────────────────────────
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.COUNT_ACTIVE_ESCALATION_REFS = exports.COUNT_WORKFLOW_REFS = exports.COUNT_CHAIN_REFS = exports.COUNT_USER_ROLE_REFS = exports.LIST_ROLES_WITH_DETAILS = exports.GET_ROLE_LIST_SCHEMA_CURRENT = exports.GET_ROLE_LIST_SCHEMA_VERSION = exports.LIST_ROLE_LIST_SCHEMA_VERSIONS = exports.GET_ROLE_SCHEMA_CURRENT = exports.GET_ROLE_SCHEMA_VERSION = exports.LIST_ROLE_SCHEMA_VERSIONS = exports.GET_ROLE_UPSTREAMS = exports.GET_ROLE_PROPERTIES = exports.GET_ROLE_METADATA_SCHEMA = exports.LIST_ENFORCING_ROLES = exports.GET_ROLE_FORM_SCHEMA = exports.UPDATE_ROLE_METADATA = exports.DELETE_ROLE = exports.LIST_ROLES = exports.ENSURE_ROLE_EXISTS = exports.CHECK_ESCALATION_CHAIN_EXISTS = exports.DELETE_ESCALATION_CHAINS_BY_SOURCE = exports.DELETE_ESCALATION_CHAIN = exports.ADD_ESCALATION_CHAIN = exports.INSERT_ESCALATION_CHAIN = exports.GET_ALL_ESCALATION_CHAINS = exports.GET_ESCALATION_TARGETS = void 0;
4
+ exports.COUNT_ACTIVE_ESCALATION_REFS = exports.COUNT_WORKFLOW_REFS = exports.COUNT_CHAIN_REFS = exports.COUNT_USER_ROLE_REFS = exports.LIST_ROLES_WITH_DETAILS = exports.GET_ROLE_LIST_SCHEMA_CURRENT = exports.GET_ROLE_LIST_SCHEMA_VERSION = exports.LIST_ROLE_LIST_SCHEMA_VERSIONS = exports.GET_ROLE_SCHEMA_CURRENT = exports.GET_ROLE_SCHEMA_VERSION = exports.LIST_ROLE_SCHEMA_VERSIONS = exports.GET_ROLE_UPSTREAMS = exports.GET_ROLE_PROPERTIES = exports.GET_ROLE_METADATA_SCHEMA = exports.LIST_ENFORCING_ROLES = exports.GET_ROLE_FORM_SCHEMA = exports.UPDATE_ROLE_METADATA = exports.DELETE_ROLE = exports.GET_ROLE_ROW = exports.LIST_CONFIGURED_ROLES = exports.LIST_ROLES = exports.ENSURE_ROLE_EXISTS = exports.CHECK_ESCALATION_CHAIN_EXISTS = exports.DELETE_ESCALATION_CHAINS_BY_SOURCE = exports.DELETE_ESCALATION_CHAIN = exports.ADD_ESCALATION_CHAIN = exports.INSERT_ESCALATION_CHAIN = exports.GET_ALL_ESCALATION_CHAINS = exports.GET_ESCALATION_TARGETS = void 0;
5
5
  exports.GET_ESCALATION_TARGETS = `
6
6
  SELECT target_role
7
7
  FROM lt_config_role_escalations
@@ -47,6 +47,29 @@ exports.ENSURE_ROLE_EXISTS = `
47
47
  ON CONFLICT DO NOTHING`;
48
48
  exports.LIST_ROLES = `
49
49
  SELECT role FROM lt_roles ORDER BY role`;
50
+ /**
51
+ * Roles carrying real configuration (a title or a form schema). Bare FK rows
52
+ * and membership-only roles are excluded — the startup orphan report uses
53
+ * this so auto-ensured rows never read as drift.
54
+ */
55
+ exports.LIST_CONFIGURED_ROLES = `
56
+ SELECT role FROM lt_roles
57
+ WHERE title IS NOT NULL OR form_schema IS NOT NULL
58
+ ORDER BY role`;
59
+ /** One role's full metadata row (plus upstream inputs) for startup diffing. */
60
+ exports.GET_ROLE_ROW = `
61
+ SELECT
62
+ r.role, r.title, r.description, r.form_schema, r.metadata_schema,
63
+ r.properties, r.ops_visible, r.ops_home_default, r.parent_role,
64
+ r.sla_minutes, r.target_per_hour, r.worker_count,
65
+ r.priority_threshold_minutes, r.priority_facet,
66
+ r.entity_facet, r.entity_state_source,
67
+ r.list_schema, r.default_pins, r.enforce_schema,
68
+ r.current_schema_version, r.current_list_schema_version,
69
+ COALESCE((SELECT array_agg(u.upstream_role ORDER BY u.upstream_role)
70
+ FROM lt_role_upstreams u WHERE u.role = r.role), '{}') AS upstream_roles
71
+ FROM lt_roles r
72
+ WHERE r.role = $1`;
50
73
  exports.DELETE_ROLE = `
51
74
  DELETE FROM lt_roles WHERE role = $1`;
52
75
  /**
@@ -4,19 +4,23 @@ export interface EscalationChain {
4
4
  target_role: string;
5
5
  }
6
6
  /**
7
- * Reserved keys inside `lt_roles.properties` the role's verb semantics for
8
- * the domain dictionary. Everything else in the bag is user-owned. Values are
9
- * plain strings; readers type-guard (the bag is open, PATCH can write anything).
7
+ * Reserved keys inside `lt_roles.properties`. Everything else in the bag is
8
+ * user-owned. Readers type-guard (the bag is open, PATCH can write anything).
10
9
  *
11
10
  * - ON_CANCEL: what cancel MEANS for this row class, imperative
12
11
  * (e.g. "reset — the dispatcher re-mints the attempt").
13
12
  * - ON_TIMEOUT: expiry semantics for this row class.
14
13
  * - WORKED_BY: who works this queue ("humans at the dashboard" | "machines" | "both").
14
+ * - KIOSK: boolean — the locked station viewport. When a signed-in user is a
15
+ * MEMBER of exactly this one role, the dashboard drops the left nav, the
16
+ * role's escalation list becomes home, and navigation is held to the list,
17
+ * the detail page, and the scan screens. Multi-role users get full chrome.
15
18
  */
16
19
  export declare const ROLE_PROPERTY_KEYS: {
17
20
  readonly ON_CANCEL: "on_cancel";
18
21
  readonly ON_TIMEOUT: "on_timeout";
19
22
  readonly WORKED_BY: "worked_by";
23
+ readonly KIOSK: "kiosk";
20
24
  };
21
25
  export interface RoleDetail {
22
26
  role: string;
@@ -3,17 +3,21 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.ROLE_PROPERTY_KEYS = void 0;
5
5
  /**
6
- * Reserved keys inside `lt_roles.properties` the role's verb semantics for
7
- * the domain dictionary. Everything else in the bag is user-owned. Values are
8
- * plain strings; readers type-guard (the bag is open, PATCH can write anything).
6
+ * Reserved keys inside `lt_roles.properties`. Everything else in the bag is
7
+ * user-owned. Readers type-guard (the bag is open, PATCH can write anything).
9
8
  *
10
9
  * - ON_CANCEL: what cancel MEANS for this row class, imperative
11
10
  * (e.g. "reset — the dispatcher re-mints the attempt").
12
11
  * - ON_TIMEOUT: expiry semantics for this row class.
13
12
  * - WORKED_BY: who works this queue ("humans at the dashboard" | "machines" | "both").
13
+ * - KIOSK: boolean — the locked station viewport. When a signed-in user is a
14
+ * MEMBER of exactly this one role, the dashboard drops the left nav, the
15
+ * role's escalation list becomes home, and navigation is held to the list,
16
+ * the detail page, and the scan screens. Multi-role users get full chrome.
14
17
  */
15
18
  exports.ROLE_PROPERTY_KEYS = {
16
19
  ON_CANCEL: 'on_cancel',
17
20
  ON_TIMEOUT: 'on_timeout',
18
21
  WORKED_BY: 'worked_by',
22
+ KIOSK: 'kiosk',
19
23
  };
@@ -20,6 +20,8 @@ export interface TopicSubscriber {
20
20
  topic: string;
21
21
  reaction_type: string;
22
22
  }
23
+ /** Config-declared topic names — the startup orphan report's candidate set. */
24
+ export declare function listConfigTopicNames(): Promise<string[]>;
23
25
  export declare function listTopics(filters?: {
24
26
  category?: string;
25
27
  search?: string;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listConfigTopicNames = listConfigTopicNames;
3
4
  exports.listTopics = listTopics;
4
5
  exports.getTopic = getTopic;
5
6
  exports.createTopic = createTopic;
@@ -10,6 +11,12 @@ exports.seedTopic = seedTopic;
10
11
  exports.resetTopic = resetTopic;
11
12
  const db_1 = require("../../lib/db");
12
13
  const sql_1 = require("./sql");
14
+ /** Config-declared topic names — the startup orphan report's candidate set. */
15
+ async function listConfigTopicNames() {
16
+ const pool = (0, db_1.getPool)();
17
+ const { rows } = await pool.query(sql_1.LIST_CONFIG_TOPIC_NAMES);
18
+ return rows.map((r) => r.topic);
19
+ }
13
20
  async function listTopics(filters = {}) {
14
21
  const pool = (0, db_1.getPool)();
15
22
  const limit = filters.limit ?? 50;
@@ -1,5 +1,7 @@
1
1
  export declare const LIST_TOPICS = "\n SELECT t.*,\n COALESCE(s.sub_count, 0)::int AS subscriber_count\n FROM lt_topic_catalog t\n LEFT JOIN LATERAL (\n SELECT COUNT(*)::int AS sub_count\n FROM lt_agent_subscriptions\n WHERE enabled = true\n AND (\n topic = t.topic\n OR t.topic LIKE REPLACE(REPLACE(topic, '*', '%'), '>', '%')\n )\n ) s ON true\n WHERE ($1::text IS NULL OR t.category = $1)\n AND ($2::text IS NULL OR t.topic ILIKE '%' || $2 || '%' OR t.description ILIKE '%' || $2 || '%')\n ORDER BY t.category, t.topic\n LIMIT $3 OFFSET $4\n";
2
2
  export declare const COUNT_TOPICS = "\n SELECT COUNT(*)::int AS total FROM lt_topic_catalog\n WHERE ($1::text IS NULL OR category = $1)\n AND ($2::text IS NULL OR topic ILIKE '%' || $2 || '%' OR description ILIKE '%' || $2 || '%')\n";
3
+ /** Config-declared topic names — the startup orphan report's candidate set. */
4
+ export declare const LIST_CONFIG_TOPIC_NAMES = "\n SELECT topic FROM lt_topic_catalog WHERE source = 'config' ORDER BY topic\n";
3
5
  export declare const GET_TOPIC = "\n SELECT * FROM lt_topic_catalog WHERE topic = $1\n";
4
6
  export declare const INSERT_TOPIC = "\n INSERT INTO lt_topic_catalog\n (topic, description, category, payload_schema, example_payload, source, tags)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n RETURNING *\n";
5
7
  export declare const UPDATE_TOPIC = "\n UPDATE lt_topic_catalog SET\n description = COALESCE($2, description),\n category = COALESCE($3, category),\n payload_schema = COALESCE($4, payload_schema),\n example_payload = COALESCE($5, example_payload),\n tags = COALESCE($6, tags)\n WHERE topic = $1\n RETURNING *\n";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LIST_SUBSCRIBERS = exports.RESET_TOPIC = exports.SEED_TOPIC = exports.UPSERT_ON_PUBLISH = exports.DELETE_TOPIC = exports.UPDATE_TOPIC = exports.INSERT_TOPIC = exports.GET_TOPIC = exports.COUNT_TOPICS = exports.LIST_TOPICS = void 0;
3
+ exports.LIST_SUBSCRIBERS = exports.RESET_TOPIC = exports.SEED_TOPIC = exports.UPSERT_ON_PUBLISH = exports.DELETE_TOPIC = exports.UPDATE_TOPIC = exports.INSERT_TOPIC = exports.GET_TOPIC = exports.LIST_CONFIG_TOPIC_NAMES = exports.COUNT_TOPICS = exports.LIST_TOPICS = void 0;
4
4
  exports.LIST_TOPICS = `
5
5
  SELECT t.*,
6
6
  COALESCE(s.sub_count, 0)::int AS subscriber_count
@@ -24,6 +24,10 @@ exports.COUNT_TOPICS = `
24
24
  WHERE ($1::text IS NULL OR category = $1)
25
25
  AND ($2::text IS NULL OR topic ILIKE '%' || $2 || '%' OR description ILIKE '%' || $2 || '%')
26
26
  `;
27
+ /** Config-declared topic names — the startup orphan report's candidate set. */
28
+ exports.LIST_CONFIG_TOPIC_NAMES = `
29
+ SELECT topic FROM lt_topic_catalog WHERE source = 'config' ORDER BY topic
30
+ `;
27
31
  exports.GET_TOPIC = `
28
32
  SELECT * FROM lt_topic_catalog WHERE topic = $1
29
33
  `;
@@ -2,6 +2,7 @@ import type { LTTopicConfig } from '../../types';
2
2
  export declare function seedSystemTopics(): Promise<void>;
3
3
  /**
4
4
  * Seed user-declared topics from startConfig.topics[].
5
- * Respects `reset: true` — overwrites DB from config on every boot.
5
+ * Ownership per entry: `reset ?? defaultReset` — code-owned topics are
6
+ * overwritten from config on every boot; db-owned topics are insert-if-absent.
6
7
  */
7
- export declare function seedConfigTopics(topics: LTTopicConfig[]): Promise<void>;
8
+ export declare function seedConfigTopics(topics: LTTopicConfig[], defaultReset?: boolean): Promise<void>;
@@ -314,9 +314,10 @@ async function seedSystemTopics() {
314
314
  }
315
315
  /**
316
316
  * Seed user-declared topics from startConfig.topics[].
317
- * Respects `reset: true` — overwrites DB from config on every boot.
317
+ * Ownership per entry: `reset ?? defaultReset` — code-owned topics are
318
+ * overwritten from config on every boot; db-owned topics are insert-if-absent.
318
319
  */
319
- async function seedConfigTopics(topics) {
320
+ async function seedConfigTopics(topics, defaultReset = false) {
320
321
  for (const def of topics) {
321
322
  try {
322
323
  const payload = {
@@ -328,7 +329,7 @@ async function seedConfigTopics(topics) {
328
329
  source: 'config',
329
330
  tags: def.tags ?? [],
330
331
  };
331
- if (def.reset) {
332
+ if (def.reset ?? defaultReset) {
332
333
  await (0, index_1.resetTopic)(payload);
333
334
  logger_1.loggerRegistry.info(`[long-tail] topic reset: ${def.topic} (config)`);
334
335
  }
@@ -1,6 +1,6 @@
1
1
  export { CreateUserInput, UpdateUserInput, VALID_ROLE_TYPES } from './types';
2
2
  export { createUser, getUser, getUserByExternalId, getUserByEmail, getUserByMetadataValue, updateUser, deleteUser, listUsers, } from './crud';
3
- export { isValidRoleType, addUserRole, removeUserRole, getUserRoles, hasRole, hasRoleType, } from './roles';
3
+ export { isValidRoleType, addUserRole, grantRoleIfAbsent, removeUserRole, getUserRoles, hasRole, hasRoleType, } from './roles';
4
4
  export { isSuperAdmin, isGroupAdmin, canManageRole, hasGlobalEscalationAccess, hasRolesAsAdmin, getRoleScope, getRoleWriteScope } from './rbac';
5
5
  export { READ_SCOPES, WRITE_SCOPES, DEFAULT_READ_SCOPE, DEFAULT_WRITE_SCOPE, isValidReadScope, isValidWriteScope, isValidScopePair, effectiveScope, } from './scope';
6
6
  export { verifyPassword } from './auth';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PREFERENCES_MAX_BYTES = exports.patchPreferences = exports.getPreferences = exports.seedAdmin = exports.verifyPassword = exports.effectiveScope = exports.isValidScopePair = exports.isValidWriteScope = exports.isValidReadScope = exports.DEFAULT_WRITE_SCOPE = exports.DEFAULT_READ_SCOPE = exports.WRITE_SCOPES = exports.READ_SCOPES = exports.getRoleWriteScope = exports.getRoleScope = exports.hasRolesAsAdmin = exports.hasGlobalEscalationAccess = exports.canManageRole = exports.isGroupAdmin = exports.isSuperAdmin = exports.hasRoleType = exports.hasRole = exports.getUserRoles = exports.removeUserRole = exports.addUserRole = exports.isValidRoleType = exports.listUsers = exports.deleteUser = exports.updateUser = exports.getUserByMetadataValue = exports.getUserByEmail = exports.getUserByExternalId = exports.getUser = exports.createUser = exports.VALID_ROLE_TYPES = void 0;
3
+ exports.PREFERENCES_MAX_BYTES = exports.patchPreferences = exports.getPreferences = exports.seedAdmin = exports.verifyPassword = exports.effectiveScope = exports.isValidScopePair = exports.isValidWriteScope = exports.isValidReadScope = exports.DEFAULT_WRITE_SCOPE = exports.DEFAULT_READ_SCOPE = exports.WRITE_SCOPES = exports.READ_SCOPES = exports.getRoleWriteScope = exports.getRoleScope = exports.hasRolesAsAdmin = exports.hasGlobalEscalationAccess = exports.canManageRole = exports.isGroupAdmin = exports.isSuperAdmin = exports.hasRoleType = exports.hasRole = exports.getUserRoles = exports.removeUserRole = exports.grantRoleIfAbsent = exports.addUserRole = exports.isValidRoleType = exports.listUsers = exports.deleteUser = exports.updateUser = exports.getUserByMetadataValue = exports.getUserByEmail = exports.getUserByExternalId = exports.getUser = exports.createUser = exports.VALID_ROLE_TYPES = void 0;
4
4
  var types_1 = require("./types");
5
5
  Object.defineProperty(exports, "VALID_ROLE_TYPES", { enumerable: true, get: function () { return types_1.VALID_ROLE_TYPES; } });
6
6
  var crud_1 = require("./crud");
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "listUsers", { enumerable: true, get: function ()
15
15
  var roles_1 = require("./roles");
16
16
  Object.defineProperty(exports, "isValidRoleType", { enumerable: true, get: function () { return roles_1.isValidRoleType; } });
17
17
  Object.defineProperty(exports, "addUserRole", { enumerable: true, get: function () { return roles_1.addUserRole; } });
18
+ Object.defineProperty(exports, "grantRoleIfAbsent", { enumerable: true, get: function () { return roles_1.grantRoleIfAbsent; } });
18
19
  Object.defineProperty(exports, "removeUserRole", { enumerable: true, get: function () { return roles_1.removeUserRole; } });
19
20
  Object.defineProperty(exports, "getUserRoles", { enumerable: true, get: function () { return roles_1.getUserRoles; } });
20
21
  Object.defineProperty(exports, "hasRole", { enumerable: true, get: function () { return roles_1.hasRole; } });
@@ -6,6 +6,14 @@ export interface RoleScopeInput {
6
6
  write_scope?: LTWriteScope;
7
7
  }
8
8
  export declare function addUserRole(userId: string, role: string, type: LTRoleType, scope?: RoleScopeInput): Promise<LTUserRole>;
9
+ /**
10
+ * Grant a role ONLY when the user does not already hold it — the scope-safe
11
+ * add for sync paths (SSO provisioning, claim-time provisioning). An existing
12
+ * grant is never touched: identity sync adds missing memberships, it never
13
+ * mutates the type/scope/provenance an admin set. Returns the inserted row, or
14
+ * null when the membership already existed.
15
+ */
16
+ export declare function grantRoleIfAbsent(userId: string, role: string, type: LTRoleType, scope?: RoleScopeInput): Promise<LTUserRole | null>;
9
17
  export declare function removeUserRole(userId: string, role: string): Promise<boolean>;
10
18
  export declare function getUserRoles(userId: string): Promise<LTUserRole[]>;
11
19
  export declare function hasRole(userId: string, role: string): Promise<boolean>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isValidRoleType = isValidRoleType;
4
4
  exports.addUserRole = addUserRole;
5
+ exports.grantRoleIfAbsent = grantRoleIfAbsent;
5
6
  exports.removeUserRole = removeUserRole;
6
7
  exports.getUserRoles = getUserRoles;
7
8
  exports.hasRole = hasRole;
@@ -36,6 +37,34 @@ async function addUserRole(userId, role, type, scope) {
36
37
  client.release();
37
38
  }
38
39
  }
40
+ /**
41
+ * Grant a role ONLY when the user does not already hold it — the scope-safe
42
+ * add for sync paths (SSO provisioning, claim-time provisioning). An existing
43
+ * grant is never touched: identity sync adds missing memberships, it never
44
+ * mutates the type/scope/provenance an admin set. Returns the inserted row, or
45
+ * null when the membership already existed.
46
+ */
47
+ async function grantRoleIfAbsent(userId, role, type, scope) {
48
+ const eff = (0, scope_1.effectiveScope)(type, scope?.read_scope ?? scope_1.DEFAULT_READ_SCOPE, scope?.write_scope ?? scope_1.DEFAULT_WRITE_SCOPE);
49
+ const pool = (0, db_1.getPool)();
50
+ // Same ensure-role + insert transaction as addUserRole: the FK target and the
51
+ // assignment commit together.
52
+ const client = await pool.connect();
53
+ try {
54
+ await client.query('BEGIN');
55
+ await client.query(sql_1.ENSURE_ROLE_EXISTS, [role]);
56
+ const { rows } = await client.query(sql_1.INSERT_USER_ROLE_IF_ABSENT, [userId, role, type, eff.read, eff.write]);
57
+ await client.query('COMMIT');
58
+ return rows[0] ?? null;
59
+ }
60
+ catch (err) {
61
+ await client.query('ROLLBACK');
62
+ throw err;
63
+ }
64
+ finally {
65
+ client.release();
66
+ }
67
+ }
39
68
  async function removeUserRole(userId, role) {
40
69
  const pool = (0, db_1.getPool)();
41
70
  const { rowCount } = await pool.query(sql_1.DELETE_USER_ROLE, [userId, role]);
@@ -37,6 +37,12 @@ export declare const GET_ROLES_BY_USER_IDS = "SELECT r.user_id, r.role, r.type,
37
37
  * A direct grant takes the row over: granted_by_persona resets to NULL, so a
38
38
  * later persona unassign can never remove a membership an admin set by hand. */
39
39
  export declare const UPSERT_USER_ROLE = "INSERT INTO lt_user_roles (user_id, role, type, read_scope, write_scope) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (user_id, role) DO UPDATE SET\n type = EXCLUDED.type,\n read_scope = EXCLUDED.read_scope,\n write_scope = EXCLUDED.write_scope,\n granted_by_persona = NULL\n RETURNING role, type, read_scope, write_scope, created_at";
40
+ /** Insert a user–role assignment ONLY when absent. The scope-safe grant for
41
+ * sync paths (SSO, claim-time provisioning): an existing row — whatever its
42
+ * type/scope/provenance — is left untouched; zero rows return on conflict.
43
+ * Identity sync may add a missing membership but never mutates authorization
44
+ * an admin already set. */
45
+ export declare const INSERT_USER_ROLE_IF_ABSENT = "INSERT INTO lt_user_roles (user_id, role, type, read_scope, write_scope) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (user_id, role) DO NOTHING\n RETURNING role, type, read_scope, write_scope, created_at";
40
46
  export declare const DELETE_USER_ROLE = "DELETE FROM lt_user_roles WHERE user_id = $1 AND role = $2";
41
47
  export declare const HAS_ROLE = "SELECT 1 FROM lt_user_roles WHERE user_id = $1 AND role = $2 LIMIT 1";
42
48
  export declare const HAS_ROLE_TYPE = "SELECT 1 FROM lt_user_roles WHERE user_id = $1 AND type = $2 LIMIT 1";
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // ─── Shared queries (used by multiple modules) ──────────────────────────────
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.IS_GROUP_ADMIN = exports.GET_ROLE_SCOPE = exports.HAS_ROLE_TYPE = exports.HAS_ROLE = exports.DELETE_USER_ROLE = exports.UPSERT_USER_ROLE = exports.GET_ROLES_BY_USER_IDS = exports.DELETE_USER_BY_ID = exports.GET_USER_WITH_ROLES = exports.VERIFY_USER_BY_ID = exports.GET_USERS_BY_METADATA_VALUE = exports.GET_USER_BY_ID = exports.GET_USER_BY_EMAIL = exports.CREATE_USER_WITH_ROLES = exports.GET_USER_BY_EXTERNAL_ID = exports.GET_ROLES_BY_USER_ID = exports.ENSURE_ROLE_EXISTS = void 0;
4
+ exports.IS_GROUP_ADMIN = exports.GET_ROLE_SCOPE = exports.HAS_ROLE_TYPE = exports.HAS_ROLE = exports.DELETE_USER_ROLE = exports.INSERT_USER_ROLE_IF_ABSENT = exports.UPSERT_USER_ROLE = exports.GET_ROLES_BY_USER_IDS = exports.DELETE_USER_BY_ID = exports.GET_USER_WITH_ROLES = exports.VERIFY_USER_BY_ID = exports.GET_USERS_BY_METADATA_VALUE = exports.GET_USER_BY_ID = exports.GET_USER_BY_EMAIL = exports.CREATE_USER_WITH_ROLES = exports.GET_USER_BY_EXTERNAL_ID = exports.GET_ROLES_BY_USER_ID = exports.ENSURE_ROLE_EXISTS = void 0;
5
5
  /** Ensure a role name exists in the lt_roles lookup table (FK target). */
6
6
  exports.ENSURE_ROLE_EXISTS = 'INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING';
7
7
  /** Fetch roles for a single user, ordered by creation time. granted_by_persona
@@ -74,6 +74,14 @@ exports.UPSERT_USER_ROLE = `INSERT INTO lt_user_roles (user_id, role, type, read
74
74
  write_scope = EXCLUDED.write_scope,
75
75
  granted_by_persona = NULL
76
76
  RETURNING role, type, read_scope, write_scope, created_at`;
77
+ /** Insert a user–role assignment ONLY when absent. The scope-safe grant for
78
+ * sync paths (SSO, claim-time provisioning): an existing row — whatever its
79
+ * type/scope/provenance — is left untouched; zero rows return on conflict.
80
+ * Identity sync may add a missing membership but never mutates authorization
81
+ * an admin already set. */
82
+ exports.INSERT_USER_ROLE_IF_ABSENT = `INSERT INTO lt_user_roles (user_id, role, type, read_scope, write_scope) VALUES ($1, $2, $3, $4, $5)
83
+ ON CONFLICT (user_id, role) DO NOTHING
84
+ RETURNING role, type, read_scope, write_scope, created_at`;
77
85
  exports.DELETE_USER_ROLE = 'DELETE FROM lt_user_roles WHERE user_id = $1 AND role = $2';
78
86
  exports.HAS_ROLE = 'SELECT 1 FROM lt_user_roles WHERE user_id = $1 AND role = $2 LIMIT 1';
79
87
  exports.HAS_ROLE_TYPE = 'SELECT 1 FROM lt_user_roles WHERE user_id = $1 AND type = $2 LIMIT 1';
@@ -49,13 +49,21 @@ async function ssoProvision(identity, ssoConfig) {
49
49
  throw err;
50
50
  }
51
51
  }
52
- /** Ensure all resolved roles exist on an already-provisioned user. */
52
+ /**
53
+ * Ensure all resolved roles exist on an already-provisioned user.
54
+ *
55
+ * SSO synchronizes IDENTITY, never authorization scope: a missing membership
56
+ * is added, an existing one is left exactly as the admin set it (type, scope,
57
+ * persona provenance). The has-check keeps the steady-state login read-only;
58
+ * `grantRoleIfAbsent` (INSERT … DO NOTHING) is the database-level guarantee
59
+ * for anything that races past it.
60
+ */
53
61
  async function syncExistingUser(userId, ltRoles) {
54
62
  const currentRoles = await (0, roles_1.getUserRoles)(userId);
55
63
  for (const lr of ltRoles) {
56
64
  const has = currentRoles.some((r) => r.role === lr.role);
57
65
  if (!has) {
58
- await (0, roles_1.addUserRole)(userId, lr.role, lr.type);
66
+ await (0, roles_1.grantRoleIfAbsent)(userId, lr.role, lr.type);
59
67
  }
60
68
  }
61
69
  const updatedRoles = await (0, roles_1.getUserRoles)(userId);
@@ -69,8 +77,11 @@ function resolveRoles(hostRoles, defaultRoleType, roleMap) {
69
77
  const mapped = roleMap
70
78
  ? hostRoles.filter((r) => roleMap[r]).map((r) => roleMap[r])
71
79
  : hostRoles;
80
+ // A configured roleMap is the complete contract: an unmapped host role means
81
+ // "no LT role" — never an injected default. The default membership applies
82
+ // only when no roleMap exists (raw host roles pass through, empty = default).
72
83
  if (mapped.length === 0) {
73
- return [{ role: defaultRoleType, type: defaultRoleType }];
84
+ return roleMap ? [] : [{ role: defaultRoleType, type: defaultRoleType }];
74
85
  }
75
86
  return mapped.map((role) => ({
76
87
  role,
@@ -0,0 +1,26 @@
1
+ /** Who owns declared configuration after first boot. */
2
+ export type ConfigSource = 'code' | 'db';
3
+ /**
4
+ * Effective ownership for one declared entry: the per-entry `reset` flag wins
5
+ * in either direction; otherwise the global `configSource` decides.
6
+ */
7
+ export declare function ownedByCode(reset: boolean | undefined, source: ConfigSource): boolean;
8
+ /** Outcome of one entry's startup pass. */
9
+ export type ApplyOutcome = 'applied' | 'unchanged' | 'db-owned';
10
+ /** Per-surface tally of a boot's config pass. */
11
+ export interface SurfaceReport {
12
+ applied: string[];
13
+ unchanged: string[];
14
+ dbOwned: string[];
15
+ /** DB rows of this surface not declared in code (reported, never deleted). */
16
+ orphans: string[];
17
+ }
18
+ export declare function newSurfaceReport(): SurfaceReport;
19
+ /** Record one entry's outcome on the surface report. */
20
+ export declare function recordOutcome(report: SurfaceReport, name: string, outcome: ApplyOutcome): void;
21
+ export declare function withConfigLock<T>(fn: () => Promise<T>): Promise<T>;
22
+ /**
23
+ * One boot line per surface. Warn level when orphans exist so operators see
24
+ * "declared set no longer matches the DB" without grepping.
25
+ */
26
+ export declare function logSurfaceReport(surface: string, report: SurfaceReport): void;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ownedByCode = ownedByCode;
4
+ exports.newSurfaceReport = newSurfaceReport;
5
+ exports.recordOutcome = recordOutcome;
6
+ exports.withConfigLock = withConfigLock;
7
+ exports.logSurfaceReport = logSurfaceReport;
8
+ const db_1 = require("../lib/db");
9
+ const logger_1 = require("../lib/logger");
10
+ /**
11
+ * Effective ownership for one declared entry: the per-entry `reset` flag wins
12
+ * in either direction; otherwise the global `configSource` decides.
13
+ */
14
+ function ownedByCode(reset, source) {
15
+ return reset ?? source === 'code';
16
+ }
17
+ function newSurfaceReport() {
18
+ return { applied: [], unchanged: [], dbOwned: [], orphans: [] };
19
+ }
20
+ /** Record one entry's outcome on the surface report. */
21
+ function recordOutcome(report, name, outcome) {
22
+ if (outcome === 'applied')
23
+ report.applied.push(name);
24
+ else if (outcome === 'unchanged')
25
+ report.unchanged.push(name);
26
+ else
27
+ report.dbOwned.push(name);
28
+ }
29
+ /**
30
+ * Serializes multi-statement config writes (role-list replaces, escalation
31
+ * target replaces) across concurrently booting containers. Single-statement
32
+ * upserts are individually race-safe; the lock exists for the replace
33
+ * transactions and keeps boot reports coherent. Distinct from the migration
34
+ * lock so a slow migration on one container never blocks another's config
35
+ * pass behind two locks at once.
36
+ */
37
+ const CONFIG_APPLY_LOCK_ID = 8675310;
38
+ async function withConfigLock(fn) {
39
+ const pool = (0, db_1.getPool)();
40
+ const client = await pool.connect();
41
+ try {
42
+ await client.query(`SELECT pg_advisory_lock(${CONFIG_APPLY_LOCK_ID})`);
43
+ return await fn();
44
+ }
45
+ finally {
46
+ await client
47
+ .query(`SELECT pg_advisory_unlock(${CONFIG_APPLY_LOCK_ID})`)
48
+ .catch(() => { });
49
+ client.release();
50
+ }
51
+ }
52
+ /**
53
+ * One boot line per surface. Warn level when orphans exist so operators see
54
+ * "declared set no longer matches the DB" without grepping.
55
+ */
56
+ function logSurfaceReport(surface, report) {
57
+ const counts = `applied ${report.applied.length}, unchanged ${report.unchanged.length}, ` +
58
+ `db-owned ${report.dbOwned.length}`;
59
+ if (report.orphans.length > 0) {
60
+ logger_1.loggerRegistry.warn(`[long-tail] config apply (${surface}): ${counts}, orphans: [${report.orphans.join(', ')}]`);
61
+ }
62
+ else {
63
+ logger_1.loggerRegistry.info(`[long-tail] config apply (${surface}): ${counts}`);
64
+ }
65
+ }
@@ -103,7 +103,14 @@ async function applyServerAuthConfig(startConfig) {
103
103
  // SSO — store config for requireAuth fallback and /api/auth/sso exchange
104
104
  if (startConfig.auth?.sso) {
105
105
  const { setSSOConfig } = await Promise.resolve().then(() => __importStar(require('../modules/sso')));
106
- setSSOConfig(startConfig.auth.sso);
106
+ const sso = { ...startConfig.auth.sso };
107
+ // A keepalive beat re-runs resolve + provisioning per tab; clamp runaway
108
+ // intervals (a 1s misconfiguration = one exchange per second per tab).
109
+ if (sso.keepaliveSeconds != null && sso.keepaliveSeconds > 0 && sso.keepaliveSeconds < 15) {
110
+ logger_1.loggerRegistry.warn(`[long-tail] sso.keepaliveSeconds ${sso.keepaliveSeconds}s clamped to 15s`);
111
+ sso.keepaliveSeconds = 15;
112
+ }
113
+ setSSOConfig(sso);
107
114
  logger_1.loggerRegistry.info('[long-tail] SSO enabled — host auth will be trusted');
108
115
  }
109
116
  }