@hotmeshio/long-tail 0.10.9 → 0.10.10
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.
- package/build/api/index.d.ts +1 -0
- package/build/api/index.js +2 -1
- package/build/api/personas.d.ts +121 -0
- package/build/api/personas.js +341 -0
- package/build/bin/ltc.js +23 -0
- package/build/lib/cli/commands/personas.d.ts +16 -0
- package/build/lib/cli/commands/personas.js +42 -0
- package/build/lib/db/schemas/024_personas.sql +53 -0
- package/build/routes/index.js +2 -0
- package/build/routes/personas.d.ts +2 -0
- package/build/routes/personas.js +118 -0
- package/build/routes/users.js +35 -0
- package/build/sdk/index.d.ts +15 -0
- package/build/sdk/index.js +16 -0
- package/build/services/persona/index.d.ts +92 -0
- package/build/services/persona/index.js +217 -0
- package/build/services/persona/sql.d.ts +24 -0
- package/build/services/persona/sql.js +158 -0
- package/build/services/persona/types.d.ts +23 -0
- package/build/services/persona/types.js +33 -0
- package/build/services/user/sql.d.ts +8 -5
- package/build/services/user/sql.js +15 -5
- package/build/system/mcp-servers/admin/index.d.ts +1 -0
- package/build/system/mcp-servers/admin/index.js +5 -2
- package/build/system/mcp-servers/admin/personas.d.ts +8 -0
- package/build/system/mcp-servers/admin/personas.js +186 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +91 -0
- package/build/system/mcp-servers/admin/schemas.js +42 -2
- package/build/system/seed/tool-manifests-admin.d.ts +975 -0
- package/build/system/seed/tool-manifests-admin.js +11 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/persona.d.ts +56 -0
- package/build/types/persona.js +2 -0
- package/build/types/user.d.ts +2 -0
- package/dashboard/dist/assets/{AdminDashboard-DWSYdAeW.js → AdminDashboard-cGYuppUP.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-DWSYdAeW.js.map → AdminDashboard-cGYuppUP.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-Dm6gL76m.js → AgentConfigPage-DwaUyoCy.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-Dm6gL76m.js.map → AgentConfigPage-DwaUyoCy.js.map} +1 -1
- package/dashboard/dist/assets/AgentDetailPage-DxpFaltM.js +4 -0
- package/dashboard/dist/assets/AgentDetailPage-DxpFaltM.js.map +1 -0
- package/dashboard/dist/assets/{AgentsPage-DKaDRk-Y.js → AgentsPage-ByOoA8oT.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-DKaDRk-Y.js.map → AgentsPage-ByOoA8oT.js.map} +1 -1
- package/dashboard/dist/assets/AutoGrowTextarea-y-oNY51_.js +2 -0
- package/dashboard/dist/assets/AutoGrowTextarea-y-oNY51_.js.map +1 -0
- package/dashboard/dist/assets/{AvailableEscalationsPage-B-aHvGKD.js → AvailableEscalationsPage-C1vtTvqP.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-B-aHvGKD.js.map → AvailableEscalationsPage-C1vtTvqP.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-DRv_orzI.js → BotPicker-Dohv8dhR.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-DRv_orzI.js.map → BotPicker-Dohv8dhR.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-DqRVzMol.js → CapabilitiesPage-DeijFG3J.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-DqRVzMol.js.map → CapabilitiesPage-DeijFG3J.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-D0psvw8i.js → CollapsibleSection-BPNwS4eO.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-D0psvw8i.js.map → CollapsibleSection-BPNwS4eO.js.map} +1 -1
- package/dashboard/dist/assets/CountdownTimer-BF71ldGF.js +2 -0
- package/dashboard/dist/assets/CountdownTimer-BF71ldGF.js.map +1 -0
- package/dashboard/dist/assets/{CredentialsPage-Axdci1z-.js → CredentialsPage-oeNmMzTM.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-Axdci1z-.js.map → CredentialsPage-oeNmMzTM.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-BjZ_wZA8.js → CronLabel-CebR1m2k.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-BjZ_wZA8.js.map → CronLabel-CebR1m2k.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-CkUCMUfk.js → CustomDurationPicker-Bh9QdBVw.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-CkUCMUfk.js.map → CustomDurationPicker-Bh9QdBVw.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-_tmC3jEe.js → DropZone-Bavzt2QR.js} +2 -2
- package/dashboard/dist/assets/{DropZone-_tmC3jEe.js.map → DropZone-Bavzt2QR.js.map} +1 -1
- package/dashboard/dist/assets/ElapsedCell-Caa6CU_y.js +2 -0
- package/dashboard/dist/assets/{ElapsedCell-BE_CN5sP.js.map → ElapsedCell-Caa6CU_y.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-CgcVIiYR.js → EscalationListSchemaPage-CAGfq0eK.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-CgcVIiYR.js.map → EscalationListSchemaPage-CAGfq0eK.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-BaAn1Hcr.js → EscalationSchemaPage-qDD4ur6P.js} +3 -3
- package/dashboard/dist/assets/{EscalationSchemaPage-BaAn1Hcr.js.map → EscalationSchemaPage-qDD4ur6P.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-Dbq7tKzr.js → EscalationsOverview-CC1wRTzy.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-Dbq7tKzr.js.map → EscalationsOverview-CC1wRTzy.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-0RUK9jqt.js → EventTable-4JeTcXr_.js} +2 -2
- package/dashboard/dist/assets/{EventTable-0RUK9jqt.js.map → EventTable-4JeTcXr_.js.map} +1 -1
- package/dashboard/dist/assets/{FilterBar-B_dy2Jhj.js → FilterBar-BOV8DWIO.js} +2 -2
- package/dashboard/dist/assets/{FilterBar-B_dy2Jhj.js.map → FilterBar-BOV8DWIO.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-CvLYp1nL.js → GraphInvokePage-C0i1speW.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-CvLYp1nL.js.map → GraphInvokePage-C0i1speW.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-B6s1ZeyA.js → HomePage-HPCd8SjF.js} +2 -2
- package/dashboard/dist/assets/{HomePage-B6s1ZeyA.js.map → HomePage-HPCd8SjF.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-DVhlAKZ3.js → ListToolbar-BFHVOba6.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-DVhlAKZ3.js.map → ListToolbar-BFHVOba6.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-Ca8cpc8g.js → McpOverview-Chn2zIXr.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-Ca8cpc8g.js.map → McpOverview-Chn2zIXr.js.map} +1 -1
- package/dashboard/dist/assets/McpQueryDetailPage-Cv1FKrvw.js +5 -0
- package/dashboard/dist/assets/McpQueryDetailPage-Cv1FKrvw.js.map +1 -0
- package/dashboard/dist/assets/McpQueryPage-Dzn8JE4E.js +2 -0
- package/dashboard/dist/assets/McpQueryPage-Dzn8JE4E.js.map +1 -0
- package/dashboard/dist/assets/{McpRunDetailPage-C76mfUtG.js → McpRunDetailPage-CuNtR4Ed.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-C76mfUtG.js.map → McpRunDetailPage-CuNtR4Ed.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-B43BqEyw.js → McpRunsPage-C8iifeKN.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-B43BqEyw.js.map → McpRunsPage-C8iifeKN.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-B5gHotsd.js → NamespacePill-ep9Zmokr.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-B5gHotsd.js.map → NamespacePill-ep9Zmokr.js.map} +1 -1
- package/dashboard/dist/assets/{OperationsPage-DbHzQCuE.js → OperationsPage-flrZ-EJ9.js} +2 -2
- package/dashboard/dist/assets/{OperationsPage-DbHzQCuE.js.map → OperationsPage-flrZ-EJ9.js.map} +1 -1
- package/dashboard/dist/assets/OperatorDashboard-Dhs2eo8p.js +2 -0
- package/dashboard/dist/assets/{OperatorDashboard-B70nS_Ah.js.map → OperatorDashboard-Dhs2eo8p.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-DrbNMGxm.js → PageHeader-Bw8RNkqR.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-DrbNMGxm.js.map → PageHeader-Bw8RNkqR.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-SbQkaQLG.js → PageHeaderWithStats-NR_4idn6.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-SbQkaQLG.js.map → PageHeaderWithStats-NR_4idn6.js.map} +1 -1
- package/dashboard/dist/assets/PersonaDetailPage-DKILoUqL.js +2 -0
- package/dashboard/dist/assets/PersonaDetailPage-DKILoUqL.js.map +1 -0
- package/dashboard/dist/assets/PersonasPage-qvzHTrFw.js +2 -0
- package/dashboard/dist/assets/PersonasPage-qvzHTrFw.js.map +1 -0
- package/dashboard/dist/assets/ProcessDetailPage-C3LfeKvi.js +2 -0
- package/dashboard/dist/assets/ProcessDetailPage-C3LfeKvi.js.map +1 -0
- package/dashboard/dist/assets/{ProcessesListPage-Di0n8TR4.js → ProcessesListPage-OoZ_u4ty.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-Di0n8TR4.js.map → ProcessesListPage-OoZ_u4ty.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-D70jTLZm.js +8 -0
- package/dashboard/dist/assets/RoleDetailPage-D70jTLZm.js.map +1 -0
- package/dashboard/dist/assets/{RolePill-Cz-AAWqA.js → RolePill-TySZq7dB.js} +2 -2
- package/dashboard/dist/assets/{RolePill-Cz-AAWqA.js.map → RolePill-TySZq7dB.js.map} +1 -1
- package/dashboard/dist/assets/RolesPage-CNueMVuJ.js +2 -0
- package/dashboard/dist/assets/RolesPage-CNueMVuJ.js.map +1 -0
- package/dashboard/dist/assets/{RunAsSelector-DFv0VNVZ.js → RunAsSelector-CwWEpiSF.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-DFv0VNVZ.js.map → RunAsSelector-CwWEpiSF.js.map} +1 -1
- package/dashboard/dist/assets/ScopeBadge-C7Uixtmk.js +2 -0
- package/dashboard/dist/assets/ScopeBadge-C7Uixtmk.js.map +1 -0
- package/dashboard/dist/assets/StatCard-Ca-ngCaD.js +2 -0
- package/dashboard/dist/assets/StatCard-Ca-ngCaD.js.map +1 -0
- package/dashboard/dist/assets/{StatusBadge-BbqghFMi.js → StatusBadge-BwUvykDo.js} +2 -2
- package/dashboard/dist/assets/{StatusBadge-BbqghFMi.js.map → StatusBadge-BwUvykDo.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-D5zxe_l9.js → StickyPagination-DvIF3kQK.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-D5zxe_l9.js.map → StickyPagination-DvIF3kQK.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-BmBfNSEr.js → StreamMessageDetail-DkFK_XGD.js} +2 -2
- package/dashboard/dist/assets/StreamMessageDetail-DkFK_XGD.js.map +1 -0
- package/dashboard/dist/assets/{SwimlaneTimeline-CA0TKDIi.js → SwimlaneTimeline-CMw-Et6h.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-CA0TKDIi.js.map → SwimlaneTimeline-CMw-Et6h.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-Bf9dZSG6.js → TagInput-D8gSp48D.js} +2 -2
- package/dashboard/dist/assets/{TagInput-Bf9dZSG6.js.map → TagInput-D8gSp48D.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-C15nADUW.js → TaskDetailPage-BhXuChOf.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-C15nADUW.js.map → TaskDetailPage-BhXuChOf.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-DfiBYQy2.js → TaskQueuePill-CHqolBmU.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-DfiBYQy2.js.map → TaskQueuePill-CHqolBmU.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-DQdAayqc.js → TasksListPage-6JHQQ5QZ.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-DQdAayqc.js.map → TasksListPage-6JHQQ5QZ.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-CvslqTB7.js → TimeAgo-CInx-vzY.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-CvslqTB7.js.map → TimeAgo-CInx-vzY.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-BZCQQONl.js → TimestampCell-Fq17Zrsu.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-BZCQQONl.js.map → TimestampCell-Fq17Zrsu.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-gA4sXukc.js → ToolPill-BbgMDhaE.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-gA4sXukc.js.map → ToolPill-BbgMDhaE.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-BgLVc6R5.js → ToolTestPanel-DhjEe59s.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-BgLVc6R5.js.map → ToolTestPanel-DhjEe59s.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-9A0aPb4t.js → TopicDetailPage-DbIdmQpQ.js} +3 -3
- package/dashboard/dist/assets/{TopicDetailPage-9A0aPb4t.js.map → TopicDetailPage-DbIdmQpQ.js.map} +1 -1
- package/dashboard/dist/assets/TopicsPage-CAP-GUbr.js +2 -0
- package/dashboard/dist/assets/{TopicsPage-o0EZxKbR.js.map → TopicsPage-CAP-GUbr.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-DKMLxBJ5.js → UserName-DggcLkSc.js} +2 -2
- package/dashboard/dist/assets/{UserName-DKMLxBJ5.js.map → UserName-DggcLkSc.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-C-snB_dD.js → WorkflowExecutionPage-Bmra5fVt.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-C-snB_dD.js.map → WorkflowExecutionPage-Bmra5fVt.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-Djt2AtZk.js → WorkflowPill-CcUUVugA.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-Djt2AtZk.js.map → WorkflowPill-CcUUVugA.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-BRQmgsLR.js → WorkflowsDashboard-BQsZAGYU.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-BRQmgsLR.js.map → WorkflowsDashboard-BQsZAGYU.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-DmMdIRHQ.js → WorkflowsOverview-CeliraHZ.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-DmMdIRHQ.js.map → WorkflowsOverview-CeliraHZ.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bj7QkCry.js → YamlWorkflowDetailPage-DGxlonrV.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bj7QkCry.js.map → YamlWorkflowDetailPage-DGxlonrV.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-BIi77mE0.js → YamlWorkflowsPage-DnT8UiyQ.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-BIi77mE0.js.map → YamlWorkflowsPage-DnT8UiyQ.js.map} +1 -1
- package/dashboard/dist/assets/{agents-EBIsjLCr.js → agents-mRV7m4sh.js} +2 -2
- package/dashboard/dist/assets/{agents-EBIsjLCr.js.map → agents-mRV7m4sh.js.map} +1 -1
- package/dashboard/dist/assets/{bots-BXzxybS6.js → bots-SONCZlCM.js} +2 -2
- package/dashboard/dist/assets/{bots-BXzxybS6.js.map → bots-SONCZlCM.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-CnprG6q_.js → capabilities-DZjCMN32.js} +2 -2
- package/dashboard/dist/assets/{capabilities-CnprG6q_.js.map → capabilities-DZjCMN32.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-DiC0xtNq.js → controlplane-RQNXjna2.js} +2 -2
- package/dashboard/dist/assets/{controlplane-DiC0xtNq.js.map → controlplane-RQNXjna2.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-D0aYpJkF.js → escalation-columns-XLEcZRCg.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-D0aYpJkF.js.map → escalation-columns-XLEcZRCg.js.map} +1 -1
- package/dashboard/dist/assets/index-2BwHxjCw.css +1 -0
- package/dashboard/dist/assets/{index-CcMIca5t.js → index-C6GsrvX1.js} +2 -2
- package/dashboard/dist/assets/{index-CcMIca5t.js.map → index-C6GsrvX1.js.map} +1 -1
- package/dashboard/dist/assets/{index-C7KY8GTF.js → index-CK-6m8e_.js} +2 -2
- package/dashboard/dist/assets/{index-C7KY8GTF.js.map → index-CK-6m8e_.js.map} +1 -1
- package/dashboard/dist/assets/index-CkeSxuZ0.js +2 -0
- package/dashboard/dist/assets/{index-CjBdB8Ht.js.map → index-CkeSxuZ0.js.map} +1 -1
- package/dashboard/dist/assets/{index-B6ldRx_s.js → index-CxVGlkK6.js} +2 -2
- package/dashboard/dist/assets/{index-B6ldRx_s.js.map → index-CxVGlkK6.js.map} +1 -1
- package/dashboard/dist/assets/{index-DVmC_UfQ.js → index-D2CfSx_x.js} +4 -4
- package/dashboard/dist/assets/{index-DVmC_UfQ.js.map → index-D2CfSx_x.js.map} +1 -1
- package/dashboard/dist/assets/{index-BHAjQoOy.js → index-DFJF8aWz.js} +3 -3
- package/dashboard/dist/assets/{index-BHAjQoOy.js.map → index-DFJF8aWz.js.map} +1 -1
- package/dashboard/dist/assets/index-DYKRtstT.js +2 -0
- package/dashboard/dist/assets/index-DYKRtstT.js.map +1 -0
- package/dashboard/dist/assets/index-DY_hlZCQ.js +2 -0
- package/dashboard/dist/assets/index-DY_hlZCQ.js.map +1 -0
- package/dashboard/dist/assets/{index-Dht0ihGT.js → index-DbZpL10z.js} +2 -2
- package/dashboard/dist/assets/{index-Dht0ihGT.js.map → index-DbZpL10z.js.map} +1 -1
- package/dashboard/dist/assets/{index-CSTwyrwu.js → index-Dh3CVChT.js} +18 -18
- package/dashboard/dist/assets/index-Dh3CVChT.js.map +1 -0
- package/dashboard/dist/assets/{index-gBnGEubX.js → index-DqsLfO-0.js} +2 -2
- package/dashboard/dist/assets/{index-gBnGEubX.js.map → index-DqsLfO-0.js.map} +1 -1
- package/dashboard/dist/assets/{index-BzRXMicE.js → index-iHV3d7o3.js} +2 -2
- package/dashboard/dist/assets/{index-BzRXMicE.js.map → index-iHV3d7o3.js.map} +1 -1
- package/dashboard/dist/assets/{index-ovFEZ9SV.js → index-ymm58cTW.js} +2 -2
- package/dashboard/dist/assets/{index-ovFEZ9SV.js.map → index-ymm58cTW.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-W5QSKkYQ.js → knowledge-Bawo8u7l.js} +2 -2
- package/dashboard/dist/assets/{knowledge-W5QSKkYQ.js.map → knowledge-Bawo8u7l.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-n877HyOj.js → mcp-CKIpbpRZ.js} +2 -2
- package/dashboard/dist/assets/{mcp-n877HyOj.js.map → mcp-CKIpbpRZ.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-C9kV4sUm.js → mcp-query-vld4hYxQ.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-C9kV4sUm.js.map → mcp-query-vld4hYxQ.js.map} +1 -1
- package/dashboard/dist/assets/personas-BsARc3df.js +2 -0
- package/dashboard/dist/assets/personas-BsARc3df.js.map +1 -0
- package/dashboard/dist/assets/{pipelines-CBR68T8R.js → pipelines-BTSL-5ko.js} +2 -2
- package/dashboard/dist/assets/{pipelines-CBR68T8R.js.map → pipelines-BTSL-5ko.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-DHpWqKMk.js → tasks-DggQdNbj.js} +2 -2
- package/dashboard/dist/assets/{tasks-DHpWqKMk.js.map → tasks-DggQdNbj.js.map} +1 -1
- package/dashboard/dist/assets/{topics-CaLtyqSh.js → topics-DzAKa34X.js} +2 -2
- package/dashboard/dist/assets/{topics-CaLtyqSh.js.map → topics-DzAKa34X.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-B8p56JlB.js → useEventHooks-5uI9oI5W.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-B8p56JlB.js.map → useEventHooks-5uI9oI5W.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-ej71FCvu.js → useNamespace-CEKzFIqe.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-ej71FCvu.js.map → useNamespace-CEKzFIqe.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-DgpiFXib.js → useYamlActivityEvents-hHgX1Z50.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-DgpiFXib.js.map → useYamlActivityEvents-hHgX1Z50.js.map} +1 -1
- package/dashboard/dist/assets/{users-CHnjI_Ca.js → users-AO8lUyNr.js} +2 -2
- package/dashboard/dist/assets/{users-CHnjI_Ca.js.map → users-AO8lUyNr.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-Bg6eJX4Z.js → vendor-icons-DN9yGSuM.js} +141 -131
- package/dashboard/dist/assets/vendor-icons-DN9yGSuM.js.map +1 -0
- package/dashboard/dist/assets/{workflows-DtnXUL0U.js → workflows-PtnlRK09.js} +2 -2
- package/dashboard/dist/assets/{workflows-DtnXUL0U.js.map → workflows-PtnlRK09.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-DDclYAID.js → yaml-workflows-DPu3Y8vG.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-DDclYAID.js.map → yaml-workflows-DPu3Y8vG.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/api/http/personas.md +160 -0
- package/docs/cli.md +12 -0
- package/docs/dashboard.md +16 -1
- package/docs/design-principles.md +9 -5
- package/docs/hitl/x-lt-list-schema.md +2 -3
- package/docs/iam.md +47 -0
- package/docs/sdk.md +1 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/AgentDetailPage-D-Xbyrb6.js +0 -4
- package/dashboard/dist/assets/AgentDetailPage-D-Xbyrb6.js.map +0 -1
- package/dashboard/dist/assets/CountdownTimer-BhF3oqS8.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-BhF3oqS8.js.map +0 -1
- package/dashboard/dist/assets/ElapsedCell-BE_CN5sP.js +0 -2
- package/dashboard/dist/assets/McpQueryDetailPage-D0r2lyZg.js +0 -5
- package/dashboard/dist/assets/McpQueryDetailPage-D0r2lyZg.js.map +0 -1
- package/dashboard/dist/assets/McpQueryPage-DPP6BB3d.js +0 -2
- package/dashboard/dist/assets/McpQueryPage-DPP6BB3d.js.map +0 -1
- package/dashboard/dist/assets/OperatorDashboard-B70nS_Ah.js +0 -2
- package/dashboard/dist/assets/ProcessDetailPage-eLLUCWFf.js +0 -2
- package/dashboard/dist/assets/ProcessDetailPage-eLLUCWFf.js.map +0 -1
- package/dashboard/dist/assets/RoleDetailPage-Dhn9zU5l.js +0 -8
- package/dashboard/dist/assets/RoleDetailPage-Dhn9zU5l.js.map +0 -1
- package/dashboard/dist/assets/RolesPage-BnAwjUiG.js +0 -2
- package/dashboard/dist/assets/RolesPage-BnAwjUiG.js.map +0 -1
- package/dashboard/dist/assets/StatCard-DBANWIss.js +0 -2
- package/dashboard/dist/assets/StatCard-DBANWIss.js.map +0 -1
- package/dashboard/dist/assets/StreamMessageDetail-BmBfNSEr.js.map +0 -1
- package/dashboard/dist/assets/TopicsPage-o0EZxKbR.js +0 -2
- package/dashboard/dist/assets/index-B2iZG_HP.css +0 -1
- package/dashboard/dist/assets/index-CSTwyrwu.js.map +0 -1
- package/dashboard/dist/assets/index-CjBdB8Ht.js +0 -2
- package/dashboard/dist/assets/index-D-7mT0ew.js +0 -2
- package/dashboard/dist/assets/index-D-7mT0ew.js.map +0 -1
- package/dashboard/dist/assets/index-DDA7FvqT.js +0 -2
- package/dashboard/dist/assets/index-DDA7FvqT.js.map +0 -1
- package/dashboard/dist/assets/vendor-icons-Bg6eJX4Z.js.map +0 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const express_1 = require("express");
|
|
37
|
+
const auth_1 = require("../modules/auth");
|
|
38
|
+
const api = __importStar(require("../api/personas"));
|
|
39
|
+
const router = (0, express_1.Router)();
|
|
40
|
+
// Personas are role bundles — a management surface. Reads and writes share the
|
|
41
|
+
// role-management gate (admin type, superadmin, engineer), the same audience
|
|
42
|
+
// that manages roles and users.
|
|
43
|
+
router.use(auth_1.requireRoleManager);
|
|
44
|
+
/**
|
|
45
|
+
* GET /api/personas
|
|
46
|
+
* List all personas with role links and holder counts.
|
|
47
|
+
*/
|
|
48
|
+
router.get('/', async (_req, res) => {
|
|
49
|
+
const result = await api.listPersonas();
|
|
50
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* POST /api/personas
|
|
54
|
+
* Create a persona.
|
|
55
|
+
* Body: { key, title?, description? }
|
|
56
|
+
*/
|
|
57
|
+
router.post('/', async (req, res) => {
|
|
58
|
+
const result = await api.createPersona(req.body || {});
|
|
59
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* POST /api/personas/seed
|
|
63
|
+
* Declarative, idempotent seed — upsert personas, sync role links, reconcile holders.
|
|
64
|
+
* Body: { personas: [{ key, title?, description?, roles: [{ role, relationship }] }] }
|
|
65
|
+
*/
|
|
66
|
+
router.post('/seed', async (req, res) => {
|
|
67
|
+
const result = await api.seedPersonas(req.body || {});
|
|
68
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* GET /api/personas/:key
|
|
72
|
+
* Get one persona with role links and assignees.
|
|
73
|
+
*/
|
|
74
|
+
router.get('/:key', async (req, res) => {
|
|
75
|
+
const result = await api.getPersona({ key: req.params.key });
|
|
76
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* PATCH /api/personas/:key
|
|
80
|
+
* Update title/description (PATCH semantics; null clears).
|
|
81
|
+
*/
|
|
82
|
+
router.patch('/:key', async (req, res) => {
|
|
83
|
+
const result = await api.updatePersona({ key: req.params.key, ...(req.body || {}) });
|
|
84
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* DELETE /api/personas/:key
|
|
88
|
+
* Delete a persona. Sustained memberships are removed or re-homed; direct grants untouched.
|
|
89
|
+
*/
|
|
90
|
+
router.delete('/:key', async (req, res) => {
|
|
91
|
+
const result = await api.deletePersona({ key: req.params.key });
|
|
92
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* PUT /api/personas/:key/roles/:role
|
|
96
|
+
* Link a role (or change its relationship).
|
|
97
|
+
* Body: { relationship: 'write-all' | 'write-self' | 'read-all' } ('write-none' = read-all)
|
|
98
|
+
*/
|
|
99
|
+
router.put('/:key/roles/:role', async (req, res) => {
|
|
100
|
+
const result = await api.linkPersonaRole({
|
|
101
|
+
key: req.params.key,
|
|
102
|
+
role: req.params.role,
|
|
103
|
+
relationship: (req.body || {}).relationship,
|
|
104
|
+
});
|
|
105
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* DELETE /api/personas/:key/roles/:role
|
|
109
|
+
* Unlink a role from a persona.
|
|
110
|
+
*/
|
|
111
|
+
router.delete('/:key/roles/:role', async (req, res) => {
|
|
112
|
+
const result = await api.unlinkPersonaRole({
|
|
113
|
+
key: req.params.key,
|
|
114
|
+
role: req.params.role,
|
|
115
|
+
});
|
|
116
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
117
|
+
});
|
|
118
|
+
exports.default = router;
|
package/build/routes/users.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
const express_1 = require("express");
|
|
37
37
|
const auth_1 = require("../modules/auth");
|
|
38
38
|
const api = __importStar(require("../api/users"));
|
|
39
|
+
const personasApi = __importStar(require("../api/personas"));
|
|
39
40
|
const router = (0, express_1.Router)();
|
|
40
41
|
// ── User CRUD ─────────────────────────────────────────────────────────────────
|
|
41
42
|
/**
|
|
@@ -144,4 +145,38 @@ router.delete('/:id/roles/:role', auth_1.requireAdmin, async (req, res) => {
|
|
|
144
145
|
const result = await api.removeUserRole({ id: req.params.id, role: req.params.role });
|
|
145
146
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
146
147
|
});
|
|
148
|
+
// ── Persona assignment ───────────────────────────────────────────────────────
|
|
149
|
+
// A persona assignment is shorthand for scoped role memberships, so the gate is
|
|
150
|
+
// the persona-management gate (admin type, superadmin, engineer).
|
|
151
|
+
/**
|
|
152
|
+
* GET /api/users/:id/personas
|
|
153
|
+
* The personas a user holds plus the composed role/scope map.
|
|
154
|
+
*/
|
|
155
|
+
router.get('/:id/personas', async (req, res) => {
|
|
156
|
+
const result = await personasApi.getUserPersonas({ id: req.params.id });
|
|
157
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
158
|
+
});
|
|
159
|
+
/**
|
|
160
|
+
* POST /api/users/:id/personas
|
|
161
|
+
* Assign a persona to a user (idempotent; re-assigning overlays fresh).
|
|
162
|
+
* Body: { persona: '<key>' }
|
|
163
|
+
*/
|
|
164
|
+
router.post('/:id/personas', auth_1.requireRoleManager, async (req, res) => {
|
|
165
|
+
const result = await personasApi.assignPersona({
|
|
166
|
+
id: req.params.id,
|
|
167
|
+
key: (req.body || {}).persona,
|
|
168
|
+
});
|
|
169
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* DELETE /api/users/:id/personas/:key
|
|
173
|
+
* Unassign a persona — removes only memberships the persona sustains.
|
|
174
|
+
*/
|
|
175
|
+
router.delete('/:id/personas/:key', auth_1.requireRoleManager, async (req, res) => {
|
|
176
|
+
const result = await personasApi.unassignPersona({
|
|
177
|
+
id: req.params.id,
|
|
178
|
+
key: req.params.key,
|
|
179
|
+
});
|
|
180
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
181
|
+
});
|
|
147
182
|
exports.default = router;
|
package/build/sdk/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as workflowsApi from '../api/workflows';
|
|
|
4
4
|
import * as yamlWorkflowsApi from '../api/yaml-workflows';
|
|
5
5
|
import * as usersApi from '../api/users';
|
|
6
6
|
import * as rolesApi from '../api/roles';
|
|
7
|
+
import * as personasApi from '../api/personas';
|
|
7
8
|
import * as authApi from '../api/auth';
|
|
8
9
|
import * as mcpApi from '../api/mcp';
|
|
9
10
|
import * as pipelinesApi from '../api/pipelines';
|
|
@@ -299,6 +300,20 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
299
300
|
getEscalationTargets: typeof rolesApi.getEscalationTargets;
|
|
300
301
|
replaceEscalationTargets: typeof rolesApi.replaceEscalationTargets;
|
|
301
302
|
};
|
|
303
|
+
personas: {
|
|
304
|
+
list: typeof personasApi.listPersonas;
|
|
305
|
+
get: typeof personasApi.getPersona;
|
|
306
|
+
create: typeof personasApi.createPersona;
|
|
307
|
+
update: typeof personasApi.updatePersona;
|
|
308
|
+
delete: typeof personasApi.deletePersona;
|
|
309
|
+
linkRole: typeof personasApi.linkPersonaRole;
|
|
310
|
+
unlinkRole: typeof personasApi.unlinkPersonaRole;
|
|
311
|
+
assign: typeof personasApi.assignPersona;
|
|
312
|
+
unassign: typeof personasApi.unassignPersona;
|
|
313
|
+
forUser: typeof personasApi.getUserPersonas;
|
|
314
|
+
/** Declarative, idempotent seed — the static-config twin of the API surface. */
|
|
315
|
+
seed: (personas: Parameters<typeof personasApi.seedPersonas>[0]["personas"]) => Promise<LTApiResult<any>>;
|
|
316
|
+
};
|
|
302
317
|
auth: {
|
|
303
318
|
login: typeof authApi.login;
|
|
304
319
|
};
|
package/build/sdk/index.js
CHANGED
|
@@ -42,6 +42,7 @@ const yamlWorkflowsApi = __importStar(require("../api/yaml-workflows"));
|
|
|
42
42
|
const usersApi = __importStar(require("../api/users"));
|
|
43
43
|
const meApi = __importStar(require("../api/me"));
|
|
44
44
|
const rolesApi = __importStar(require("../api/roles"));
|
|
45
|
+
const personasApi = __importStar(require("../api/personas"));
|
|
45
46
|
const authApi = __importStar(require("../api/auth"));
|
|
46
47
|
const mcpApi = __importStar(require("../api/mcp"));
|
|
47
48
|
const pipelinesApi = __importStar(require("../api/pipelines"));
|
|
@@ -217,6 +218,21 @@ function createClient(options = {}) {
|
|
|
217
218
|
getEscalationTargets: rolesApi.getEscalationTargets,
|
|
218
219
|
replaceEscalationTargets: rolesApi.replaceEscalationTargets,
|
|
219
220
|
},
|
|
221
|
+
// ── Personas — role bundles with per-role relationship scope ───────────
|
|
222
|
+
personas: {
|
|
223
|
+
list: personasApi.listPersonas,
|
|
224
|
+
get: personasApi.getPersona,
|
|
225
|
+
create: personasApi.createPersona,
|
|
226
|
+
update: personasApi.updatePersona,
|
|
227
|
+
delete: personasApi.deletePersona,
|
|
228
|
+
linkRole: personasApi.linkPersonaRole,
|
|
229
|
+
unlinkRole: personasApi.unlinkPersonaRole,
|
|
230
|
+
assign: personasApi.assignPersona,
|
|
231
|
+
unassign: personasApi.unassignPersona,
|
|
232
|
+
forUser: personasApi.getUserPersonas,
|
|
233
|
+
/** Declarative, idempotent seed — the static-config twin of the API surface. */
|
|
234
|
+
seed: (personas) => personasApi.seedPersonas({ personas }),
|
|
235
|
+
},
|
|
220
236
|
// ── Auth ───────────────────────────────────────────────────────────────
|
|
221
237
|
auth: {
|
|
222
238
|
login: authApi.login,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { LTPersonaRecord, LTPersonaRelationship, LTPersonaSpec, LTUserPersona, LTComposedRoleScope } from '../../types';
|
|
2
|
+
import type { CreatePersonaInput, UpdatePersonaInput } from './types';
|
|
3
|
+
export { PERSONA_RELATIONSHIPS, normalizeRelationship, relationshipToWriteScope, } from './types';
|
|
4
|
+
export type { CreatePersonaInput, UpdatePersonaInput } from './types';
|
|
5
|
+
/** Counts from one membership reconciliation pass. */
|
|
6
|
+
export interface PersonaRecomputeResult {
|
|
7
|
+
granted: number;
|
|
8
|
+
refreshed: number;
|
|
9
|
+
raised: number;
|
|
10
|
+
removed: number;
|
|
11
|
+
}
|
|
12
|
+
/** An assignee row on the persona detail surface. */
|
|
13
|
+
export interface PersonaAssignee {
|
|
14
|
+
id: string;
|
|
15
|
+
external_id: string;
|
|
16
|
+
display_name: string | null;
|
|
17
|
+
email: string | null;
|
|
18
|
+
assigned_at: Date;
|
|
19
|
+
}
|
|
20
|
+
export declare function listPersonas(): Promise<LTPersonaRecord[]>;
|
|
21
|
+
export declare function getPersona(key: string): Promise<(LTPersonaRecord & {
|
|
22
|
+
assignees: PersonaAssignee[];
|
|
23
|
+
}) | null>;
|
|
24
|
+
export declare function createPersona(input: CreatePersonaInput): Promise<LTPersonaRecord>;
|
|
25
|
+
export declare function updatePersona(key: string, input: UpdatePersonaInput): Promise<LTPersonaRecord | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Delete a persona. Holds are removed and memberships reconciled BEFORE the
|
|
28
|
+
* persona row is deleted, so rows it sustained are dropped (or re-homed to a
|
|
29
|
+
* sibling persona) rather than silently converting into direct grants.
|
|
30
|
+
*/
|
|
31
|
+
export declare function deletePersona(key: string): Promise<{
|
|
32
|
+
deleted: boolean;
|
|
33
|
+
recompute?: PersonaRecomputeResult;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Link a role to a persona (or update the link's relationship). The role is
|
|
37
|
+
* ensured as an FK target, and every current holder's memberships are
|
|
38
|
+
* reconciled in the same transaction.
|
|
39
|
+
*/
|
|
40
|
+
export declare function linkPersonaRole(key: string, role: string, relationship: LTPersonaRelationship): Promise<{
|
|
41
|
+
role: string;
|
|
42
|
+
relationship: LTPersonaRelationship;
|
|
43
|
+
recompute: PersonaRecomputeResult;
|
|
44
|
+
} | null>;
|
|
45
|
+
/** Unlink a role from a persona and reconcile every holder's memberships. */
|
|
46
|
+
export declare function unlinkPersonaRole(key: string, role: string): Promise<{
|
|
47
|
+
unlinked: boolean;
|
|
48
|
+
personaFound: boolean;
|
|
49
|
+
recompute?: PersonaRecomputeResult;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Assign a persona to a user. Idempotent: re-assigning overlays fresh — the
|
|
53
|
+
* user's memberships are reconciled against the persona's CURRENT role links
|
|
54
|
+
* (union with every other persona they hold; direct grants only ever raised,
|
|
55
|
+
* never lowered or removed).
|
|
56
|
+
*/
|
|
57
|
+
export declare function assignPersona(userId: string, key: string): Promise<PersonaRecomputeResult | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Unassign a persona from a user. Removes only memberships the persona
|
|
60
|
+
* sustains — rows another held persona still grants are re-homed to it, and
|
|
61
|
+
* direct grants are never touched.
|
|
62
|
+
*/
|
|
63
|
+
export declare function unassignPersona(userId: string, key: string): Promise<{
|
|
64
|
+
unassigned: boolean;
|
|
65
|
+
personaFound: boolean;
|
|
66
|
+
recompute?: PersonaRecomputeResult;
|
|
67
|
+
}>;
|
|
68
|
+
/** The personas a user holds plus the composed role/scope map their memberships form. */
|
|
69
|
+
export declare function getUserPersonas(userId: string): Promise<{
|
|
70
|
+
personas: LTUserPersona[];
|
|
71
|
+
roles: LTComposedRoleScope[];
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Declarative, idempotent persona seeding — the static-config twin of the API
|
|
75
|
+
* surface (like roles + default_pins in the same seed pass). Each spec is
|
|
76
|
+
* authoritative for its persona: title/description overlaid, role links synced
|
|
77
|
+
* (links absent from the spec are pruned), linked roles ensured as FK targets,
|
|
78
|
+
* and every holder of a seeded persona reconciled — re-running the seed after
|
|
79
|
+
* editing a spec re-applies it to everyone holding the persona.
|
|
80
|
+
*
|
|
81
|
+
* Relationships must be canonical (normalize aliases at the API boundary).
|
|
82
|
+
*/
|
|
83
|
+
export declare function seedPersonas(specs: (Omit<LTPersonaSpec, 'roles'> & {
|
|
84
|
+
roles: {
|
|
85
|
+
role: string;
|
|
86
|
+
relationship: LTPersonaRelationship;
|
|
87
|
+
}[];
|
|
88
|
+
})[]): Promise<{
|
|
89
|
+
personas: number;
|
|
90
|
+
links: number;
|
|
91
|
+
recompute: PersonaRecomputeResult;
|
|
92
|
+
}>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.relationshipToWriteScope = exports.normalizeRelationship = exports.PERSONA_RELATIONSHIPS = void 0;
|
|
4
|
+
exports.listPersonas = listPersonas;
|
|
5
|
+
exports.getPersona = getPersona;
|
|
6
|
+
exports.createPersona = createPersona;
|
|
7
|
+
exports.updatePersona = updatePersona;
|
|
8
|
+
exports.deletePersona = deletePersona;
|
|
9
|
+
exports.linkPersonaRole = linkPersonaRole;
|
|
10
|
+
exports.unlinkPersonaRole = unlinkPersonaRole;
|
|
11
|
+
exports.assignPersona = assignPersona;
|
|
12
|
+
exports.unassignPersona = unassignPersona;
|
|
13
|
+
exports.getUserPersonas = getUserPersonas;
|
|
14
|
+
exports.seedPersonas = seedPersonas;
|
|
15
|
+
const db_1 = require("../../lib/db");
|
|
16
|
+
const sql_1 = require("./sql");
|
|
17
|
+
var types_1 = require("./types");
|
|
18
|
+
Object.defineProperty(exports, "PERSONA_RELATIONSHIPS", { enumerable: true, get: function () { return types_1.PERSONA_RELATIONSHIPS; } });
|
|
19
|
+
Object.defineProperty(exports, "normalizeRelationship", { enumerable: true, get: function () { return types_1.normalizeRelationship; } });
|
|
20
|
+
Object.defineProperty(exports, "relationshipToWriteScope", { enumerable: true, get: function () { return types_1.relationshipToWriteScope; } });
|
|
21
|
+
const EMPTY_RECOMPUTE = { granted: 0, refreshed: 0, raised: 0, removed: 0 };
|
|
22
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
23
|
+
/** Run `work` inside a single transaction on a dedicated client. */
|
|
24
|
+
async function withTransaction(work) {
|
|
25
|
+
const client = await (0, db_1.getPool)().connect();
|
|
26
|
+
try {
|
|
27
|
+
await client.query('BEGIN');
|
|
28
|
+
const result = await work((text, params) => client.query(text, params));
|
|
29
|
+
await client.query('COMMIT');
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
await client.query('ROLLBACK').catch(() => { });
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
client.release();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Reconcile lt_user_roles for a set of users against their held personas. */
|
|
41
|
+
async function recomputeMemberships(query, userIds) {
|
|
42
|
+
if (userIds.length === 0)
|
|
43
|
+
return { ...EMPTY_RECOMPUTE };
|
|
44
|
+
const { rows } = await query(sql_1.RECOMPUTE_PERSONA_MEMBERSHIPS, [userIds]);
|
|
45
|
+
return rows[0];
|
|
46
|
+
}
|
|
47
|
+
async function resolvePersonaId(query, key) {
|
|
48
|
+
const { rows } = await query(sql_1.GET_PERSONA_ID_BY_KEY, [key]);
|
|
49
|
+
return rows[0]?.id ?? null;
|
|
50
|
+
}
|
|
51
|
+
// ─── Persona CRUD ─────────────────────────────────────────────────────────────
|
|
52
|
+
async function listPersonas() {
|
|
53
|
+
const { rows } = await (0, db_1.getPool)().query(sql_1.LIST_PERSONAS);
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
56
|
+
async function getPersona(key) {
|
|
57
|
+
const pool = (0, db_1.getPool)();
|
|
58
|
+
const { rows } = await pool.query(sql_1.GET_PERSONA_BY_KEY, [key]);
|
|
59
|
+
if (rows.length === 0)
|
|
60
|
+
return null;
|
|
61
|
+
const { rows: assignees } = await pool.query(sql_1.GET_PERSONA_ASSIGNEES, [rows[0].id]);
|
|
62
|
+
return { ...rows[0], assignees };
|
|
63
|
+
}
|
|
64
|
+
async function createPersona(input) {
|
|
65
|
+
const { rows } = await (0, db_1.getPool)().query(sql_1.CREATE_PERSONA, [
|
|
66
|
+
input.key,
|
|
67
|
+
input.title ?? null,
|
|
68
|
+
input.description ?? null,
|
|
69
|
+
]);
|
|
70
|
+
return { ...rows[0], roles: [], user_count: 0 };
|
|
71
|
+
}
|
|
72
|
+
async function updatePersona(key, input) {
|
|
73
|
+
const { rows } = await (0, db_1.getPool)().query(sql_1.UPDATE_PERSONA, [
|
|
74
|
+
key,
|
|
75
|
+
input.title !== undefined, input.title ?? null,
|
|
76
|
+
input.description !== undefined, input.description ?? null,
|
|
77
|
+
]);
|
|
78
|
+
if (rows.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
const detail = await getPersona(key);
|
|
81
|
+
return detail;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Delete a persona. Holds are removed and memberships reconciled BEFORE the
|
|
85
|
+
* persona row is deleted, so rows it sustained are dropped (or re-homed to a
|
|
86
|
+
* sibling persona) rather than silently converting into direct grants.
|
|
87
|
+
*/
|
|
88
|
+
async function deletePersona(key) {
|
|
89
|
+
return withTransaction(async (query) => {
|
|
90
|
+
const personaId = await resolvePersonaId(query, key);
|
|
91
|
+
if (!personaId)
|
|
92
|
+
return { deleted: false };
|
|
93
|
+
const { rows: holders } = await query(`DELETE FROM lt_user_personas WHERE persona_id = $1 RETURNING user_id`, [personaId]);
|
|
94
|
+
// Guard against drift: also reconcile any user with a row still sustained
|
|
95
|
+
// by this persona, even if no hold record remained.
|
|
96
|
+
const { rows: sustained } = await query(sql_1.LIST_SUSTAINED_USER_IDS, [personaId]);
|
|
97
|
+
const affected = [...new Set([...holders, ...sustained].map((r) => r.user_id))];
|
|
98
|
+
const recompute = await recomputeMemberships(query, affected);
|
|
99
|
+
await query(sql_1.DELETE_PERSONA, [personaId]);
|
|
100
|
+
return { deleted: true, recompute };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// ─── Role links ───────────────────────────────────────────────────────────────
|
|
104
|
+
/**
|
|
105
|
+
* Link a role to a persona (or update the link's relationship). The role is
|
|
106
|
+
* ensured as an FK target, and every current holder's memberships are
|
|
107
|
+
* reconciled in the same transaction.
|
|
108
|
+
*/
|
|
109
|
+
async function linkPersonaRole(key, role, relationship) {
|
|
110
|
+
return withTransaction(async (query) => {
|
|
111
|
+
const personaId = await resolvePersonaId(query, key);
|
|
112
|
+
if (!personaId)
|
|
113
|
+
return null;
|
|
114
|
+
await query(sql_1.ENSURE_ROLES_EXIST, [[role]]);
|
|
115
|
+
const { rows } = await query(sql_1.UPSERT_PERSONA_ROLE, [personaId, role, relationship]);
|
|
116
|
+
const { rows: holders } = await query(sql_1.LIST_PERSONA_HOLDER_IDS, [personaId]);
|
|
117
|
+
const recompute = await recomputeMemberships(query, holders.map((r) => r.user_id));
|
|
118
|
+
return { ...rows[0], recompute };
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/** Unlink a role from a persona and reconcile every holder's memberships. */
|
|
122
|
+
async function unlinkPersonaRole(key, role) {
|
|
123
|
+
return withTransaction(async (query) => {
|
|
124
|
+
const personaId = await resolvePersonaId(query, key);
|
|
125
|
+
if (!personaId)
|
|
126
|
+
return { unlinked: false, personaFound: false };
|
|
127
|
+
const { rowCount } = await query(sql_1.DELETE_PERSONA_ROLE, [personaId, role]);
|
|
128
|
+
if ((rowCount ?? 0) === 0)
|
|
129
|
+
return { unlinked: false, personaFound: true };
|
|
130
|
+
const { rows: holders } = await query(sql_1.LIST_PERSONA_HOLDER_IDS, [personaId]);
|
|
131
|
+
const recompute = await recomputeMemberships(query, holders.map((r) => r.user_id));
|
|
132
|
+
return { unlinked: true, personaFound: true, recompute };
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// ─── Assignment ───────────────────────────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* Assign a persona to a user. Idempotent: re-assigning overlays fresh — the
|
|
138
|
+
* user's memberships are reconciled against the persona's CURRENT role links
|
|
139
|
+
* (union with every other persona they hold; direct grants only ever raised,
|
|
140
|
+
* never lowered or removed).
|
|
141
|
+
*/
|
|
142
|
+
async function assignPersona(userId, key) {
|
|
143
|
+
return withTransaction(async (query) => {
|
|
144
|
+
const personaId = await resolvePersonaId(query, key);
|
|
145
|
+
if (!personaId)
|
|
146
|
+
return null;
|
|
147
|
+
await query(sql_1.INSERT_USER_PERSONA, [userId, personaId]);
|
|
148
|
+
return recomputeMemberships(query, [userId]);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Unassign a persona from a user. Removes only memberships the persona
|
|
153
|
+
* sustains — rows another held persona still grants are re-homed to it, and
|
|
154
|
+
* direct grants are never touched.
|
|
155
|
+
*/
|
|
156
|
+
async function unassignPersona(userId, key) {
|
|
157
|
+
return withTransaction(async (query) => {
|
|
158
|
+
const personaId = await resolvePersonaId(query, key);
|
|
159
|
+
if (!personaId)
|
|
160
|
+
return { unassigned: false, personaFound: false };
|
|
161
|
+
const { rowCount } = await query(sql_1.DELETE_USER_PERSONA, [userId, personaId]);
|
|
162
|
+
if ((rowCount ?? 0) === 0)
|
|
163
|
+
return { unassigned: false, personaFound: true };
|
|
164
|
+
const recompute = await recomputeMemberships(query, [userId]);
|
|
165
|
+
return { unassigned: true, personaFound: true, recompute };
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/** The personas a user holds plus the composed role/scope map their memberships form. */
|
|
169
|
+
async function getUserPersonas(userId) {
|
|
170
|
+
const pool = (0, db_1.getPool)();
|
|
171
|
+
const [{ rows: personas }, { rows: roles }] = await Promise.all([
|
|
172
|
+
pool.query(sql_1.GET_USER_PERSONAS, [userId]),
|
|
173
|
+
pool.query(sql_1.GET_USER_COMPOSED_ROLES, [userId]),
|
|
174
|
+
]);
|
|
175
|
+
return { personas, roles };
|
|
176
|
+
}
|
|
177
|
+
// ─── Declarative seed ─────────────────────────────────────────────────────────
|
|
178
|
+
/**
|
|
179
|
+
* Declarative, idempotent persona seeding — the static-config twin of the API
|
|
180
|
+
* surface (like roles + default_pins in the same seed pass). Each spec is
|
|
181
|
+
* authoritative for its persona: title/description overlaid, role links synced
|
|
182
|
+
* (links absent from the spec are pruned), linked roles ensured as FK targets,
|
|
183
|
+
* and every holder of a seeded persona reconciled — re-running the seed after
|
|
184
|
+
* editing a spec re-applies it to everyone holding the persona.
|
|
185
|
+
*
|
|
186
|
+
* Relationships must be canonical (normalize aliases at the API boundary).
|
|
187
|
+
*/
|
|
188
|
+
async function seedPersonas(specs) {
|
|
189
|
+
if (specs.length === 0)
|
|
190
|
+
return { personas: 0, links: 0, recompute: { ...EMPTY_RECOMPUTE } };
|
|
191
|
+
return withTransaction(async (query) => {
|
|
192
|
+
const allRoles = [...new Set(specs.flatMap((s) => s.roles.map((r) => r.role)))];
|
|
193
|
+
if (allRoles.length > 0) {
|
|
194
|
+
await query(sql_1.ENSURE_ROLES_EXIST, [allRoles]);
|
|
195
|
+
}
|
|
196
|
+
const personaIds = [];
|
|
197
|
+
let links = 0;
|
|
198
|
+
for (const spec of specs) {
|
|
199
|
+
const { rows } = await query(sql_1.UPSERT_PERSONA, [
|
|
200
|
+
spec.key,
|
|
201
|
+
spec.title ?? null,
|
|
202
|
+
spec.description ?? null,
|
|
203
|
+
]);
|
|
204
|
+
const personaId = rows[0].id;
|
|
205
|
+
personaIds.push(personaId);
|
|
206
|
+
await query(sql_1.SYNC_PERSONA_ROLES, [
|
|
207
|
+
personaId,
|
|
208
|
+
spec.roles.map((r) => r.role),
|
|
209
|
+
spec.roles.map((r) => r.relationship),
|
|
210
|
+
]);
|
|
211
|
+
links += spec.roles.length;
|
|
212
|
+
}
|
|
213
|
+
const { rows: holders } = await query('SELECT DISTINCT user_id FROM lt_user_personas WHERE persona_id = ANY($1::uuid[])', [personaIds]);
|
|
214
|
+
const recompute = await recomputeMemberships(query, holders.map((r) => r.user_id));
|
|
215
|
+
return { personas: specs.length, links, recompute };
|
|
216
|
+
});
|
|
217
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const LIST_PERSONAS = "SELECT p.id, p.key, p.title, p.description, r.roles, u.user_count, p.created_at, p.updated_at\n FROM lt_personas p\n LEFT JOIN LATERAL (\n SELECT COALESCE(json_agg(json_build_object('role', pr.role, 'relationship', pr.relationship)\n ORDER BY pr.created_at, pr.role), '[]'::json) AS roles\n FROM lt_persona_roles pr WHERE pr.persona_id = p.id\n ) r ON true\n LEFT JOIN LATERAL (\n SELECT COUNT(*)::int AS user_count FROM lt_user_personas up WHERE up.persona_id = p.id\n ) u ON true\n ORDER BY p.key";
|
|
2
|
+
export declare const GET_PERSONA_BY_KEY = "SELECT p.id, p.key, p.title, p.description, r.roles, u.user_count, p.created_at, p.updated_at\n FROM lt_personas p\n LEFT JOIN LATERAL (\n SELECT COALESCE(json_agg(json_build_object('role', pr.role, 'relationship', pr.relationship)\n ORDER BY pr.created_at, pr.role), '[]'::json) AS roles\n FROM lt_persona_roles pr WHERE pr.persona_id = p.id\n ) r ON true\n LEFT JOIN LATERAL (\n SELECT COUNT(*)::int AS user_count FROM lt_user_personas up WHERE up.persona_id = p.id\n ) u ON true\n WHERE p.key = $1";
|
|
3
|
+
export declare const CREATE_PERSONA = "INSERT INTO lt_personas (key, title, description) VALUES ($1, $2, $3)\n RETURNING id, key, title, description, created_at, updated_at";
|
|
4
|
+
/** Declarative upsert for the seed pass — the spec is authoritative for title/description. */
|
|
5
|
+
export declare const UPSERT_PERSONA = "INSERT INTO lt_personas (key, title, description) VALUES ($1, $2, $3)\n ON CONFLICT (key) DO UPDATE SET\n title = EXCLUDED.title,\n description = EXCLUDED.description,\n updated_at = NOW()\n RETURNING id";
|
|
6
|
+
/** PATCH semantics: $2/$4 are set-sentinels gating whether $3/$5 write. */
|
|
7
|
+
export declare const UPDATE_PERSONA = "UPDATE lt_personas SET\n title = CASE WHEN $2 THEN $3 ELSE title END,\n description = CASE WHEN $4 THEN $5 ELSE description END,\n updated_at = NOW()\n WHERE key = $1\n RETURNING id, key, title, description, created_at, updated_at";
|
|
8
|
+
export declare const DELETE_PERSONA = "DELETE FROM lt_personas WHERE id = $1";
|
|
9
|
+
export declare const GET_PERSONA_ASSIGNEES = "SELECT us.id, us.external_id, us.display_name, us.email, up.created_at AS assigned_at\n FROM lt_user_personas up\n JOIN lt_users us ON us.id = up.user_id\n WHERE up.persona_id = $1\n ORDER BY up.created_at, us.external_id";
|
|
10
|
+
export declare const UPSERT_PERSONA_ROLE = "INSERT INTO lt_persona_roles (persona_id, role, relationship) VALUES ($1, $2, $3)\n ON CONFLICT (persona_id, role) DO UPDATE SET relationship = EXCLUDED.relationship\n RETURNING role, relationship";
|
|
11
|
+
export declare const DELETE_PERSONA_ROLE = "DELETE FROM lt_persona_roles WHERE persona_id = $1 AND role = $2";
|
|
12
|
+
/** Replace a persona's links declaratively: prune links not in $2, upsert the rest. */
|
|
13
|
+
export declare const SYNC_PERSONA_ROLES = "WITH incoming AS (\n SELECT x.role, x.relationship\n FROM unnest($2::text[], $3::text[]) AS x(role, relationship)\n ), pruned AS (\n DELETE FROM lt_persona_roles pr\n WHERE pr.persona_id = $1 AND pr.role NOT IN (SELECT role FROM incoming)\n )\n INSERT INTO lt_persona_roles (persona_id, role, relationship)\n SELECT $1, role, relationship FROM incoming\n ON CONFLICT (persona_id, role) DO UPDATE SET relationship = EXCLUDED.relationship";
|
|
14
|
+
export declare const INSERT_USER_PERSONA = "INSERT INTO lt_user_personas (user_id, persona_id) VALUES ($1, $2)\n ON CONFLICT DO NOTHING";
|
|
15
|
+
export declare const DELETE_USER_PERSONA = "DELETE FROM lt_user_personas WHERE user_id = $1 AND persona_id = $2";
|
|
16
|
+
export declare const LIST_PERSONA_HOLDER_IDS = "SELECT user_id FROM lt_user_personas WHERE persona_id = $1";
|
|
17
|
+
/** Users with a membership row still sustained by this persona (drift guard for delete). */
|
|
18
|
+
export declare const LIST_SUSTAINED_USER_IDS = "SELECT DISTINCT user_id FROM lt_user_roles WHERE granted_by_persona = $1";
|
|
19
|
+
export declare const RECOMPUTE_PERSONA_MEMBERSHIPS = "WITH affected AS (\n SELECT unnest($1::uuid[]) AS user_id\n ), grants AS (\n SELECT up.user_id, pr.role,\n MAX(CASE pr.relationship WHEN 'write-all' THEN 2 WHEN 'write-self' THEN 1 ELSE 0 END) AS write_rank,\n (MIN(pr.persona_id::text))::uuid AS sustaining_persona\n FROM lt_user_personas up\n JOIN lt_persona_roles pr ON pr.persona_id = up.persona_id\n JOIN affected a ON a.user_id = up.user_id\n GROUP BY up.user_id, pr.role\n ), removed AS (\n DELETE FROM lt_user_roles ur\n USING affected a\n WHERE ur.user_id = a.user_id\n AND ur.granted_by_persona IS NOT NULL\n AND NOT EXISTS (SELECT 1 FROM grants g WHERE g.user_id = ur.user_id AND g.role = ur.role)\n RETURNING ur.user_id, ur.role\n ), refreshed AS (\n UPDATE lt_user_roles ur\n SET read_scope = 'all',\n write_scope = CASE g.write_rank WHEN 2 THEN 'all' WHEN 1 THEN 'self' ELSE 'none' END,\n granted_by_persona = g.sustaining_persona\n FROM grants g\n WHERE ur.user_id = g.user_id AND ur.role = g.role\n AND ur.granted_by_persona IS NOT NULL\n RETURNING ur.user_id, ur.role\n ), raised AS (\n UPDATE lt_user_roles ur\n SET read_scope = 'all',\n write_scope = CASE\n WHEN g.write_rank = 2 THEN 'all'\n WHEN g.write_rank = 1 AND ur.write_scope = 'none' THEN 'self'\n ELSE ur.write_scope\n END\n FROM grants g\n WHERE ur.user_id = g.user_id AND ur.role = g.role\n AND ur.granted_by_persona IS NULL\n AND (ur.read_scope <> 'all'\n OR g.write_rank > CASE ur.write_scope WHEN 'all' THEN 2 WHEN 'self' THEN 1 ELSE 0 END)\n RETURNING ur.user_id, ur.role\n ), granted AS (\n INSERT INTO lt_user_roles (user_id, role, type, read_scope, write_scope, granted_by_persona)\n SELECT g.user_id, g.role, 'member', 'all',\n CASE g.write_rank WHEN 2 THEN 'all' WHEN 1 THEN 'self' ELSE 'none' END,\n g.sustaining_persona\n FROM grants g\n WHERE NOT EXISTS (SELECT 1 FROM lt_user_roles ur WHERE ur.user_id = g.user_id AND ur.role = g.role)\n RETURNING user_id, role\n )\n SELECT\n (SELECT COUNT(*)::int FROM granted) AS granted,\n (SELECT COUNT(*)::int FROM refreshed) AS refreshed,\n (SELECT COUNT(*)::int FROM raised) AS raised,\n (SELECT COUNT(*)::int FROM removed) AS removed";
|
|
20
|
+
export declare const GET_USER_PERSONAS = "SELECT p.id, p.key, p.title, p.description, r.roles, up.created_at AS assigned_at\n FROM lt_user_personas up\n JOIN lt_personas p ON p.id = up.persona_id\n LEFT JOIN LATERAL (\n SELECT COALESCE(json_agg(json_build_object('role', pr.role, 'relationship', pr.relationship)\n ORDER BY pr.created_at, pr.role), '[]'::json) AS roles\n FROM lt_persona_roles pr WHERE pr.persona_id = p.id\n ) r ON true\n WHERE up.user_id = $1\n ORDER BY p.key";
|
|
21
|
+
/** The composed role/scope map: every membership with its sustaining persona key (null = direct). */
|
|
22
|
+
export declare const GET_USER_COMPOSED_ROLES = "SELECT ur.role, ur.read_scope, ur.write_scope, p.key AS granted_by_persona\n FROM lt_user_roles ur\n LEFT JOIN lt_personas p ON p.id = ur.granted_by_persona\n WHERE ur.user_id = $1\n ORDER BY ur.role";
|
|
23
|
+
export declare const GET_PERSONA_ID_BY_KEY = "SELECT id FROM lt_personas WHERE key = $1";
|
|
24
|
+
export declare const ENSURE_ROLES_EXIST = "INSERT INTO lt_roles (role) SELECT DISTINCT unnest($1::text[]) ON CONFLICT DO NOTHING";
|