@hotmeshio/long-tail 0.10.1 → 0.10.3

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 (311) hide show
  1. package/README.md +1 -0
  2. package/build/api/bot-accounts.d.ts +4 -0
  3. package/build/api/bot-accounts.js +3 -1
  4. package/build/api/escalations/metadata.js +21 -1
  5. package/build/api/escalations/resolve.d.ts +8 -0
  6. package/build/api/escalations/resolve.js +82 -3
  7. package/build/api/me.d.ts +16 -0
  8. package/build/api/me.js +79 -0
  9. package/build/api/roles.d.ts +8 -0
  10. package/build/api/roles.js +14 -0
  11. package/build/bin/ltc.js +10 -1
  12. package/build/lib/cli/client.d.ts +4 -1
  13. package/build/lib/cli/client.js +4 -2
  14. package/build/lib/db/schemas/022_user_preferences.sql +18 -0
  15. package/build/lib/db/schemas/023_role_enforce_schema.sql +18 -0
  16. package/build/lib/typed-resolution.d.ts +35 -0
  17. package/build/lib/typed-resolution.js +37 -0
  18. package/build/modules/config.d.ts +1 -0
  19. package/build/modules/config.js +5 -0
  20. package/build/routes/bot-accounts.js +3 -1
  21. package/build/routes/index.js +2 -0
  22. package/build/routes/me.d.ts +2 -0
  23. package/build/routes/me.js +57 -0
  24. package/build/sdk/index.d.ts +10 -0
  25. package/build/sdk/index.js +25 -0
  26. package/build/services/escalation/crud.d.ts +18 -6
  27. package/build/services/escalation/crud.js +18 -2
  28. package/build/services/escalation/index.d.ts +1 -0
  29. package/build/services/escalation/index.js +1 -0
  30. package/build/services/escalation/resolver-validation.d.ts +28 -0
  31. package/build/services/escalation/resolver-validation.js +95 -0
  32. package/build/services/escalation/sql.d.ts +12 -2
  33. package/build/services/escalation/sql.js +22 -2
  34. package/build/services/iam/bots.d.ts +4 -1
  35. package/build/services/iam/bots.js +5 -3
  36. package/build/services/iam/sql.d.ts +2 -2
  37. package/build/services/iam/sql.js +15 -1
  38. package/build/services/role/enforcement-cache.d.ts +21 -0
  39. package/build/services/role/enforcement-cache.js +0 -0
  40. package/build/services/role/index.js +6 -0
  41. package/build/services/role/sql.d.ts +15 -2
  42. package/build/services/role/sql.js +21 -2
  43. package/build/services/role/types.d.ts +25 -0
  44. package/build/services/user/index.d.ts +1 -0
  45. package/build/services/user/index.js +5 -1
  46. package/build/services/user/preferences.d.ts +15 -0
  47. package/build/services/user/preferences.js +38 -0
  48. package/build/shared/form-validation/field-validator.d.ts +45 -0
  49. package/build/shared/form-validation/field-validator.js +214 -0
  50. package/build/shared/form-validation/index.d.ts +11 -0
  51. package/build/shared/form-validation/index.js +27 -0
  52. package/build/shared/form-validation/validate-resolver-payload.d.ts +41 -0
  53. package/build/shared/form-validation/validate-resolver-payload.js +62 -0
  54. package/build/shared/form-validation/x-lt-bind.d.ts +41 -0
  55. package/build/shared/form-validation/x-lt-bind.js +110 -0
  56. package/build/shared/form-validation/x-lt-help.d.ts +21 -0
  57. package/build/shared/form-validation/x-lt-help.js +91 -0
  58. package/build/shared/form-validation/x-lt-show-if.d.ts +7 -0
  59. package/build/shared/form-validation/x-lt-show-if.js +84 -0
  60. package/build/system/mcp-servers/admin/schemas.d.ts +29 -0
  61. package/build/system/mcp-servers/admin/schemas.js +7 -0
  62. package/build/system/mcp-servers/admin/users.js +8 -3
  63. package/build/system/mcp-servers/human-queue-schemas.d.ts +4 -4
  64. package/build/system/mcp-servers/human-queue-schemas.js +4 -1
  65. package/build/system/mcp-servers/human-queue.js +41 -2
  66. package/build/system/seed/tool-manifests-admin.d.ts +167 -0
  67. package/build/system/seed/tool-manifests-admin.js +1 -1
  68. package/build/tsconfig.tsbuildinfo +1 -1
  69. package/build/types/sdk.d.ts +6 -1
  70. package/build/types/validation.d.ts +43 -0
  71. package/build/types/validation.js +28 -0
  72. package/dashboard/dist/assets/{AdminDashboard-D7Amh-EJ.js → AdminDashboard-DQLGybZ9.js} +2 -2
  73. package/dashboard/dist/assets/{AdminDashboard-D7Amh-EJ.js.map → AdminDashboard-DQLGybZ9.js.map} +1 -1
  74. package/dashboard/dist/assets/AgentConfigPage-4JgMpuYJ.js +16 -0
  75. package/dashboard/dist/assets/AgentConfigPage-4JgMpuYJ.js.map +1 -0
  76. package/dashboard/dist/assets/{AgentDetailPage-Dy3Qvlfk.js → AgentDetailPage-DCgKokgX.js} +3 -3
  77. package/dashboard/dist/assets/{AgentDetailPage-Dy3Qvlfk.js.map → AgentDetailPage-DCgKokgX.js.map} +1 -1
  78. package/dashboard/dist/assets/{AgentsPage-D7WePZ0o.js → AgentsPage-BK_8VKZO.js} +2 -2
  79. package/dashboard/dist/assets/{AgentsPage-D7WePZ0o.js.map → AgentsPage-BK_8VKZO.js.map} +1 -1
  80. package/dashboard/dist/assets/AvailableEscalationsPage-Dp1rsIHC.js +2 -0
  81. package/dashboard/dist/assets/AvailableEscalationsPage-Dp1rsIHC.js.map +1 -0
  82. package/dashboard/dist/assets/BotPicker-DvUnYEJk.js +2 -0
  83. package/dashboard/dist/assets/BotPicker-DvUnYEJk.js.map +1 -0
  84. package/dashboard/dist/assets/{CapabilitiesPage-3VTLEPGO.js → CapabilitiesPage-CJ5ldY8w.js} +2 -2
  85. package/dashboard/dist/assets/{CapabilitiesPage-3VTLEPGO.js.map → CapabilitiesPage-CJ5ldY8w.js.map} +1 -1
  86. package/dashboard/dist/assets/{CollapsibleSection-BYgeZz9M.js → CollapsibleSection-BXOpfs3z.js} +2 -2
  87. package/dashboard/dist/assets/{CollapsibleSection-BYgeZz9M.js.map → CollapsibleSection-BXOpfs3z.js.map} +1 -1
  88. package/dashboard/dist/assets/CountdownTimer-D20FNwS-.js +2 -0
  89. package/dashboard/dist/assets/CountdownTimer-D20FNwS-.js.map +1 -0
  90. package/dashboard/dist/assets/{CredentialsPage-CaH3J3SE.js → CredentialsPage-vZ2psNnd.js} +2 -2
  91. package/dashboard/dist/assets/{CredentialsPage-CaH3J3SE.js.map → CredentialsPage-vZ2psNnd.js.map} +1 -1
  92. package/dashboard/dist/assets/{CronLabel-Bma_4AC2.js → CronLabel-KRkqNSYs.js} +2 -2
  93. package/dashboard/dist/assets/{CronLabel-Bma_4AC2.js.map → CronLabel-KRkqNSYs.js.map} +1 -1
  94. package/dashboard/dist/assets/{CustomDurationPicker-BxyjEX27.js → CustomDurationPicker-C-vGsrFl.js} +2 -2
  95. package/dashboard/dist/assets/CustomDurationPicker-C-vGsrFl.js.map +1 -0
  96. package/dashboard/dist/assets/{DropZone-zxH-9B8c.js → DropZone-C3NdMtwK.js} +2 -2
  97. package/dashboard/dist/assets/{DropZone-zxH-9B8c.js.map → DropZone-C3NdMtwK.js.map} +1 -1
  98. package/dashboard/dist/assets/{ElapsedCell-Dr1cEwoi.js → ElapsedCell-DNH-XHOS.js} +2 -2
  99. package/dashboard/dist/assets/{ElapsedCell-Dr1cEwoi.js.map → ElapsedCell-DNH-XHOS.js.map} +1 -1
  100. package/dashboard/dist/assets/{EscalationListSchemaPage-CLdsR-hK.js → EscalationListSchemaPage-BIW1qxjC.js} +3 -3
  101. package/dashboard/dist/assets/{EscalationListSchemaPage-CLdsR-hK.js.map → EscalationListSchemaPage-BIW1qxjC.js.map} +1 -1
  102. package/dashboard/dist/assets/{EscalationSchemaPage-DnEnvolM.js → EscalationSchemaPage-Fj2IwQXQ.js} +2 -2
  103. package/dashboard/dist/assets/{EscalationSchemaPage-DnEnvolM.js.map → EscalationSchemaPage-Fj2IwQXQ.js.map} +1 -1
  104. package/dashboard/dist/assets/{EscalationsOverview-BH8cxgLI.js → EscalationsOverview-BVUx0vZH.js} +2 -2
  105. package/dashboard/dist/assets/{EscalationsOverview-BH8cxgLI.js.map → EscalationsOverview-BVUx0vZH.js.map} +1 -1
  106. package/dashboard/dist/assets/{EventTable-p-xsfYYg.js → EventTable-Dm4zZaRl.js} +2 -2
  107. package/dashboard/dist/assets/{EventTable-p-xsfYYg.js.map → EventTable-Dm4zZaRl.js.map} +1 -1
  108. package/dashboard/dist/assets/{GraphInvokePage-Cf9c_LJ-.js → GraphInvokePage-D4f074qh.js} +2 -2
  109. package/dashboard/dist/assets/GraphInvokePage-D4f074qh.js.map +1 -0
  110. package/dashboard/dist/assets/HomePage-DQ0bb_M_.js +2 -0
  111. package/dashboard/dist/assets/HomePage-DQ0bb_M_.js.map +1 -0
  112. package/dashboard/dist/assets/ListToolbar-CItVkLQ-.js +2 -0
  113. package/dashboard/dist/assets/{ListToolbar-CDlol8XJ.js.map → ListToolbar-CItVkLQ-.js.map} +1 -1
  114. package/dashboard/dist/assets/{McpOverview-ChZ36p9t.js → McpOverview-ByYVDldF.js} +2 -2
  115. package/dashboard/dist/assets/{McpOverview-ChZ36p9t.js.map → McpOverview-ByYVDldF.js.map} +1 -1
  116. package/dashboard/dist/assets/{McpQueryDetailPage-X4j_jm3r.js → McpQueryDetailPage-DYuIdJiH.js} +2 -2
  117. package/dashboard/dist/assets/{McpQueryDetailPage-X4j_jm3r.js.map → McpQueryDetailPage-DYuIdJiH.js.map} +1 -1
  118. package/dashboard/dist/assets/{McpQueryPage-C30HsQfZ.js → McpQueryPage-C9C4mPC0.js} +2 -2
  119. package/dashboard/dist/assets/{McpQueryPage-C30HsQfZ.js.map → McpQueryPage-C9C4mPC0.js.map} +1 -1
  120. package/dashboard/dist/assets/McpRunDetailPage-CWHKhx8n.js +2 -0
  121. package/dashboard/dist/assets/McpRunDetailPage-CWHKhx8n.js.map +1 -0
  122. package/dashboard/dist/assets/{McpRunsPage-BHZHCwMM.js → McpRunsPage-DMamEjjr.js} +2 -2
  123. package/dashboard/dist/assets/{McpRunsPage-BHZHCwMM.js.map → McpRunsPage-DMamEjjr.js.map} +1 -1
  124. package/dashboard/dist/assets/{NamespacePill-BQNMfY0m.js → NamespacePill-B3YOa1xo.js} +2 -2
  125. package/dashboard/dist/assets/{NamespacePill-BQNMfY0m.js.map → NamespacePill-B3YOa1xo.js.map} +1 -1
  126. package/dashboard/dist/assets/OperationsPage-CVPDugQz.js +2 -0
  127. package/dashboard/dist/assets/OperationsPage-CVPDugQz.js.map +1 -0
  128. package/dashboard/dist/assets/{OperatorDashboard-BGJrxLMU.js → OperatorDashboard-CK-4GREp.js} +2 -2
  129. package/dashboard/dist/assets/{OperatorDashboard-BGJrxLMU.js.map → OperatorDashboard-CK-4GREp.js.map} +1 -1
  130. package/dashboard/dist/assets/{PageHeader-BIY1YvKR.js → PageHeader-poyTWcUA.js} +2 -2
  131. package/dashboard/dist/assets/{PageHeader-BIY1YvKR.js.map → PageHeader-poyTWcUA.js.map} +1 -1
  132. package/dashboard/dist/assets/{PageHeaderWithStats-TUyglVW0.js → PageHeaderWithStats-CAmspbl9.js} +2 -2
  133. package/dashboard/dist/assets/{PageHeaderWithStats-TUyglVW0.js.map → PageHeaderWithStats-CAmspbl9.js.map} +1 -1
  134. package/dashboard/dist/assets/{ProcessDetailPage-Bcp3GKFo.js → ProcessDetailPage-BBCwv1bQ.js} +2 -2
  135. package/dashboard/dist/assets/{ProcessDetailPage-Bcp3GKFo.js.map → ProcessDetailPage-BBCwv1bQ.js.map} +1 -1
  136. package/dashboard/dist/assets/{ProcessesListPage-DPrvuLoU.js → ProcessesListPage-m70xmTZx.js} +2 -2
  137. package/dashboard/dist/assets/{ProcessesListPage-DPrvuLoU.js.map → ProcessesListPage-m70xmTZx.js.map} +1 -1
  138. package/dashboard/dist/assets/RoleDetailPage-l865Cx3n.js +8 -0
  139. package/dashboard/dist/assets/RoleDetailPage-l865Cx3n.js.map +1 -0
  140. package/dashboard/dist/assets/RolePill-C0SWz9X7.js +2 -0
  141. package/dashboard/dist/assets/RolePill-C0SWz9X7.js.map +1 -0
  142. package/dashboard/dist/assets/RolesPage-Wzfjz7Wc.js +2 -0
  143. package/dashboard/dist/assets/RolesPage-Wzfjz7Wc.js.map +1 -0
  144. package/dashboard/dist/assets/RunAsSelector-DlnIolTf.js +2 -0
  145. package/dashboard/dist/assets/{RunAsSelector-Z2Yunqdf.js.map → RunAsSelector-DlnIolTf.js.map} +1 -1
  146. package/dashboard/dist/assets/{SlidePanel-DK4sWXmp.js → SlidePanel-Bm9B6zIA.js} +2 -2
  147. package/dashboard/dist/assets/{SlidePanel-DK4sWXmp.js.map → SlidePanel-Bm9B6zIA.js.map} +1 -1
  148. package/dashboard/dist/assets/{StickyPagination-CeLsnCIS.js → StickyPagination-BHF5fTt5.js} +2 -2
  149. package/dashboard/dist/assets/{StickyPagination-CeLsnCIS.js.map → StickyPagination-BHF5fTt5.js.map} +1 -1
  150. package/dashboard/dist/assets/{StreamMessageDetail-DWs7XkmH.js → StreamMessageDetail-Dgp1lzay.js} +2 -2
  151. package/dashboard/dist/assets/{StreamMessageDetail-DWs7XkmH.js.map → StreamMessageDetail-Dgp1lzay.js.map} +1 -1
  152. package/dashboard/dist/assets/{SwimlaneTimeline-Dxl9a51q.js → SwimlaneTimeline-BlptZ8lr.js} +2 -2
  153. package/dashboard/dist/assets/{SwimlaneTimeline-Dxl9a51q.js.map → SwimlaneTimeline-BlptZ8lr.js.map} +1 -1
  154. package/dashboard/dist/assets/{TagInput-CK-JhBG2.js → TagInput-CM3AV5Ky.js} +2 -2
  155. package/dashboard/dist/assets/{TagInput-CK-JhBG2.js.map → TagInput-CM3AV5Ky.js.map} +1 -1
  156. package/dashboard/dist/assets/{TaskDetailPage-DVr31zDE.js → TaskDetailPage-f5K9dVPG.js} +2 -2
  157. package/dashboard/dist/assets/{TaskDetailPage-DVr31zDE.js.map → TaskDetailPage-f5K9dVPG.js.map} +1 -1
  158. package/dashboard/dist/assets/{TaskQueuePill-C_Vo2Clc.js → TaskQueuePill-BZYmqdcv.js} +2 -2
  159. package/dashboard/dist/assets/{TaskQueuePill-C_Vo2Clc.js.map → TaskQueuePill-BZYmqdcv.js.map} +1 -1
  160. package/dashboard/dist/assets/{TasksListPage-DNGp5lfi.js → TasksListPage-BPyPG6UY.js} +2 -2
  161. package/dashboard/dist/assets/{TasksListPage-DNGp5lfi.js.map → TasksListPage-BPyPG6UY.js.map} +1 -1
  162. package/dashboard/dist/assets/{TimeAgo-j2Q4w4lY.js → TimeAgo-CBaGbueK.js} +2 -2
  163. package/dashboard/dist/assets/{TimeAgo-j2Q4w4lY.js.map → TimeAgo-CBaGbueK.js.map} +1 -1
  164. package/dashboard/dist/assets/{TimestampCell-BLEfob2C.js → TimestampCell-Z5kDrR9U.js} +2 -2
  165. package/dashboard/dist/assets/{TimestampCell-BLEfob2C.js.map → TimestampCell-Z5kDrR9U.js.map} +1 -1
  166. package/dashboard/dist/assets/ToolPill-D_-m3lUO.js +2 -0
  167. package/dashboard/dist/assets/{ToolPill-BCi101Jh.js.map → ToolPill-D_-m3lUO.js.map} +1 -1
  168. package/dashboard/dist/assets/{ToolTestPanel-ZJZT_mDP.js → ToolTestPanel-DFrP_aMx.js} +2 -2
  169. package/dashboard/dist/assets/{ToolTestPanel-ZJZT_mDP.js.map → ToolTestPanel-DFrP_aMx.js.map} +1 -1
  170. package/dashboard/dist/assets/TopicDetailPage-Cj9qXnTW.js +9 -0
  171. package/dashboard/dist/assets/TopicDetailPage-Cj9qXnTW.js.map +1 -0
  172. package/dashboard/dist/assets/{TopicsPage-Ctb_85Tw.js → TopicsPage-Dyq6niOv.js} +2 -2
  173. package/dashboard/dist/assets/{TopicsPage-Ctb_85Tw.js.map → TopicsPage-Dyq6niOv.js.map} +1 -1
  174. package/dashboard/dist/assets/{UserName-CkFpUqVK.js → UserName-DOX2P-8M.js} +2 -2
  175. package/dashboard/dist/assets/{UserName-CkFpUqVK.js.map → UserName-DOX2P-8M.js.map} +1 -1
  176. package/dashboard/dist/assets/WorkflowExecutionPage-Cb_dSIoq.js +2 -0
  177. package/dashboard/dist/assets/WorkflowExecutionPage-Cb_dSIoq.js.map +1 -0
  178. package/dashboard/dist/assets/WorkflowPill-CyYYPB7b.js +2 -0
  179. package/dashboard/dist/assets/{WorkflowPill-DHxrAkp3.js.map → WorkflowPill-CyYYPB7b.js.map} +1 -1
  180. package/dashboard/dist/assets/{WorkflowsDashboard-DOyZbqzf.js → WorkflowsDashboard-DJpMO5IY.js} +2 -2
  181. package/dashboard/dist/assets/{WorkflowsDashboard-DOyZbqzf.js.map → WorkflowsDashboard-DJpMO5IY.js.map} +1 -1
  182. package/dashboard/dist/assets/{WorkflowsOverview-CRyHvUBx.js → WorkflowsOverview-CCC_KaXz.js} +2 -2
  183. package/dashboard/dist/assets/{WorkflowsOverview-CRyHvUBx.js.map → WorkflowsOverview-CCC_KaXz.js.map} +1 -1
  184. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DRBmQXY6.js → YamlWorkflowDetailPage-C3ntUyoZ.js} +11 -11
  185. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DRBmQXY6.js.map → YamlWorkflowDetailPage-C3ntUyoZ.js.map} +1 -1
  186. package/dashboard/dist/assets/{YamlWorkflowsPage-HnI-9eBW.js → YamlWorkflowsPage-ju-9JDXg.js} +2 -2
  187. package/dashboard/dist/assets/{YamlWorkflowsPage-HnI-9eBW.js.map → YamlWorkflowsPage-ju-9JDXg.js.map} +1 -1
  188. package/dashboard/dist/assets/{agents-BA2CvQO9.js → agents-gs9fea8r.js} +2 -2
  189. package/dashboard/dist/assets/{agents-BA2CvQO9.js.map → agents-gs9fea8r.js.map} +1 -1
  190. package/dashboard/dist/assets/bots-DYxAHrob.js +2 -0
  191. package/dashboard/dist/assets/bots-DYxAHrob.js.map +1 -0
  192. package/dashboard/dist/assets/{capabilities-BYBKLRRI.js → capabilities-BJoxz6DJ.js} +2 -2
  193. package/dashboard/dist/assets/{capabilities-BYBKLRRI.js.map → capabilities-BJoxz6DJ.js.map} +1 -1
  194. package/dashboard/dist/assets/{controlplane-H0Ljhb-1.js → controlplane-BeDZgDDf.js} +2 -2
  195. package/dashboard/dist/assets/{controlplane-H0Ljhb-1.js.map → controlplane-BeDZgDDf.js.map} +1 -1
  196. package/dashboard/dist/assets/escalation-columns-CZ4fl0IE.js +2 -0
  197. package/dashboard/dist/assets/escalation-columns-CZ4fl0IE.js.map +1 -0
  198. package/dashboard/dist/assets/index-BMM3Xug7.js +2 -0
  199. package/dashboard/dist/assets/index-BMM3Xug7.js.map +1 -0
  200. package/dashboard/dist/assets/{index-BXrm76Hm.js → index-BMXkxgnK.js} +2 -2
  201. package/dashboard/dist/assets/{index-BXrm76Hm.js.map → index-BMXkxgnK.js.map} +1 -1
  202. package/dashboard/dist/assets/{index-CWkAN6wc.js → index-Bj1Mjjiv.js} +2 -2
  203. package/dashboard/dist/assets/{index-CWkAN6wc.js.map → index-Bj1Mjjiv.js.map} +1 -1
  204. package/dashboard/dist/assets/index-BqVb13tE.js +5 -0
  205. package/dashboard/dist/assets/index-BqVb13tE.js.map +1 -0
  206. package/dashboard/dist/assets/{index-YWvrMcqh.js → index-CGrtjUdj.js} +2 -2
  207. package/dashboard/dist/assets/{index-YWvrMcqh.js.map → index-CGrtjUdj.js.map} +1 -1
  208. package/dashboard/dist/assets/{index-DSjih1iX.js → index-CQ8cd4JZ.js} +2 -2
  209. package/dashboard/dist/assets/{index-DSjih1iX.js.map → index-CQ8cd4JZ.js.map} +1 -1
  210. package/dashboard/dist/assets/{index-D1Q8WfGN.js → index-CruL35yU.js} +2 -2
  211. package/dashboard/dist/assets/{index-D1Q8WfGN.js.map → index-CruL35yU.js.map} +1 -1
  212. package/dashboard/dist/assets/index-DAM1Dm1F.js +2 -0
  213. package/dashboard/dist/assets/index-DAM1Dm1F.js.map +1 -0
  214. package/dashboard/dist/assets/{index-CP6D_Gtw.js → index-DZ7PWRg1.js} +2 -2
  215. package/dashboard/dist/assets/{index-CP6D_Gtw.js.map → index-DZ7PWRg1.js.map} +1 -1
  216. package/dashboard/dist/assets/{index-BI-9M7z3.js → index-DfbrPOP3.js} +2 -2
  217. package/dashboard/dist/assets/{index-BI-9M7z3.js.map → index-DfbrPOP3.js.map} +1 -1
  218. package/dashboard/dist/assets/index-DwuM7Fka.js +5 -0
  219. package/dashboard/dist/assets/index-DwuM7Fka.js.map +1 -0
  220. package/dashboard/dist/assets/index-DycfrrTK.js +63 -0
  221. package/dashboard/dist/assets/index-DycfrrTK.js.map +1 -0
  222. package/dashboard/dist/assets/{index-Dx6-4esw.js → index-tbvwCdvQ.js} +2 -2
  223. package/dashboard/dist/assets/{index-Dx6-4esw.js.map → index-tbvwCdvQ.js.map} +1 -1
  224. package/dashboard/dist/assets/index-zP2ncT4m.css +1 -0
  225. package/dashboard/dist/assets/{knowledge-BVbElRHf.js → knowledge-DZnI1Fdk.js} +2 -2
  226. package/dashboard/dist/assets/{knowledge-BVbElRHf.js.map → knowledge-DZnI1Fdk.js.map} +1 -1
  227. package/dashboard/dist/assets/{mcp-CstDdowJ.js → mcp-BB_m6UyI.js} +2 -2
  228. package/dashboard/dist/assets/{mcp-CstDdowJ.js.map → mcp-BB_m6UyI.js.map} +1 -1
  229. package/dashboard/dist/assets/{mcp-query-DHUs0N8H.js → mcp-query-BTSWn0de.js} +2 -2
  230. package/dashboard/dist/assets/{mcp-query-DHUs0N8H.js.map → mcp-query-BTSWn0de.js.map} +1 -1
  231. package/dashboard/dist/assets/{pipelines-BDmAzE3U.js → pipelines-CvGQLyUz.js} +2 -2
  232. package/dashboard/dist/assets/{pipelines-BDmAzE3U.js.map → pipelines-CvGQLyUz.js.map} +1 -1
  233. package/dashboard/dist/assets/task-queues-7-sHKPsN.js +2 -0
  234. package/dashboard/dist/assets/task-queues-7-sHKPsN.js.map +1 -0
  235. package/dashboard/dist/assets/{tasks--qZocuG-.js → tasks-BbgOhCVm.js} +2 -2
  236. package/dashboard/dist/assets/{tasks--qZocuG-.js.map → tasks-BbgOhCVm.js.map} +1 -1
  237. package/dashboard/dist/assets/{topics-P3jkjMsx.js → topics-D9usuYHv.js} +2 -2
  238. package/dashboard/dist/assets/{topics-P3jkjMsx.js.map → topics-D9usuYHv.js.map} +1 -1
  239. package/dashboard/dist/assets/{useEventHooks-CqH8Pasj.js → useEventHooks-DxnqYVPg.js} +2 -2
  240. package/dashboard/dist/assets/{useEventHooks-CqH8Pasj.js.map → useEventHooks-DxnqYVPg.js.map} +1 -1
  241. package/dashboard/dist/assets/{useNamespace-aI3tPfo3.js → useNamespace-CBAn0o5z.js} +2 -2
  242. package/dashboard/dist/assets/{useNamespace-aI3tPfo3.js.map → useNamespace-CBAn0o5z.js.map} +1 -1
  243. package/dashboard/dist/assets/{useYamlActivityEvents-CPVyuW8i.js → useYamlActivityEvents-blOlKGDO.js} +2 -2
  244. package/dashboard/dist/assets/{useYamlActivityEvents-CPVyuW8i.js.map → useYamlActivityEvents-blOlKGDO.js.map} +1 -1
  245. package/dashboard/dist/assets/{users-BrcmIH7O.js → users-CYVJHgNM.js} +2 -2
  246. package/dashboard/dist/assets/{users-BrcmIH7O.js.map → users-CYVJHgNM.js.map} +1 -1
  247. package/dashboard/dist/assets/{vendor-icons-Qk9qLjF_.js → vendor-icons-B98-XxNc.js} +147 -132
  248. package/dashboard/dist/assets/vendor-icons-B98-XxNc.js.map +1 -0
  249. package/dashboard/dist/assets/{workflows-DPhX5L5C.js → workflows-csQAJF0G.js} +2 -2
  250. package/dashboard/dist/assets/{workflows-DPhX5L5C.js.map → workflows-csQAJF0G.js.map} +1 -1
  251. package/dashboard/dist/assets/{yaml-workflows-KZI2cufF.js → yaml-workflows-CctsTDby.js} +2 -2
  252. package/dashboard/dist/assets/{yaml-workflows-KZI2cufF.js.map → yaml-workflows-CctsTDby.js.map} +1 -1
  253. package/dashboard/dist/index.html +3 -3
  254. package/docs/api/http/escalations.md +16 -1
  255. package/docs/api/http/roles.md +4 -0
  256. package/docs/api/http/users.md +39 -0
  257. package/docs/api/sdk/roles.md +5 -0
  258. package/docs/api/sdk/users.md +39 -0
  259. package/docs/hitl/pinned-views.md +45 -0
  260. package/docs/hitl/x-lt-list-schema.md +37 -2
  261. package/docs/hitl/x-lt-show-if.md +11 -0
  262. package/docs/hitl/x-lt-widget.md +22 -0
  263. package/docs/hitl-guide.md +9 -5
  264. package/docs/schema-enforcement.md +149 -0
  265. package/package.json +1 -1
  266. package/dashboard/dist/assets/AgentConfigPage-CW2udbSO.js +0 -16
  267. package/dashboard/dist/assets/AgentConfigPage-CW2udbSO.js.map +0 -1
  268. package/dashboard/dist/assets/AvailableEscalationsPage-Dx51E6s_.js +0 -2
  269. package/dashboard/dist/assets/AvailableEscalationsPage-Dx51E6s_.js.map +0 -1
  270. package/dashboard/dist/assets/BotPicker-KebTxTDi.js +0 -2
  271. package/dashboard/dist/assets/BotPicker-KebTxTDi.js.map +0 -1
  272. package/dashboard/dist/assets/CountdownTimer-DFqEYmiF.js +0 -2
  273. package/dashboard/dist/assets/CountdownTimer-DFqEYmiF.js.map +0 -1
  274. package/dashboard/dist/assets/CustomDurationPicker-BxyjEX27.js.map +0 -1
  275. package/dashboard/dist/assets/GraphInvokePage-Cf9c_LJ-.js.map +0 -1
  276. package/dashboard/dist/assets/HomePage-BdCtKXAV.js +0 -2
  277. package/dashboard/dist/assets/HomePage-BdCtKXAV.js.map +0 -1
  278. package/dashboard/dist/assets/ListToolbar-CDlol8XJ.js +0 -2
  279. package/dashboard/dist/assets/McpRunDetailPage-18d7Iqfy.js +0 -2
  280. package/dashboard/dist/assets/McpRunDetailPage-18d7Iqfy.js.map +0 -1
  281. package/dashboard/dist/assets/OperationsPage-GMojwL3q.js +0 -2
  282. package/dashboard/dist/assets/OperationsPage-GMojwL3q.js.map +0 -1
  283. package/dashboard/dist/assets/RoleDetailPage-Ws3umQKQ.js +0 -8
  284. package/dashboard/dist/assets/RoleDetailPage-Ws3umQKQ.js.map +0 -1
  285. package/dashboard/dist/assets/RolePill-4LO_CuO3.js +0 -2
  286. package/dashboard/dist/assets/RolePill-4LO_CuO3.js.map +0 -1
  287. package/dashboard/dist/assets/RolesPage-D3NNU92h.js +0 -2
  288. package/dashboard/dist/assets/RolesPage-D3NNU92h.js.map +0 -1
  289. package/dashboard/dist/assets/RunAsSelector-Z2Yunqdf.js +0 -2
  290. package/dashboard/dist/assets/ToolPill-BCi101Jh.js +0 -2
  291. package/dashboard/dist/assets/TopicDetailPage-ynUkO1A8.js +0 -9
  292. package/dashboard/dist/assets/TopicDetailPage-ynUkO1A8.js.map +0 -1
  293. package/dashboard/dist/assets/WorkflowExecutionPage-CSw2aQJC.js +0 -2
  294. package/dashboard/dist/assets/WorkflowExecutionPage-CSw2aQJC.js.map +0 -1
  295. package/dashboard/dist/assets/WorkflowPill-DHxrAkp3.js +0 -2
  296. package/dashboard/dist/assets/bots-DHHnoo8x.js +0 -2
  297. package/dashboard/dist/assets/bots-DHHnoo8x.js.map +0 -1
  298. package/dashboard/dist/assets/escalation-columns-BV3Ztn7D.js +0 -2
  299. package/dashboard/dist/assets/escalation-columns-BV3Ztn7D.js.map +0 -1
  300. package/dashboard/dist/assets/index-B4FaugP1.js +0 -2
  301. package/dashboard/dist/assets/index-B4FaugP1.js.map +0 -1
  302. package/dashboard/dist/assets/index-BeFjLCIQ.js +0 -5
  303. package/dashboard/dist/assets/index-BeFjLCIQ.js.map +0 -1
  304. package/dashboard/dist/assets/index-BrEcQQ1p.css +0 -1
  305. package/dashboard/dist/assets/index-CVMA5PVQ.js +0 -2
  306. package/dashboard/dist/assets/index-CVMA5PVQ.js.map +0 -1
  307. package/dashboard/dist/assets/index-O2wWcCLo.js +0 -5
  308. package/dashboard/dist/assets/index-O2wWcCLo.js.map +0 -1
  309. package/dashboard/dist/assets/index-iX8HLLB9.js +0 -63
  310. package/dashboard/dist/assets/index-iX8HLLB9.js.map +0 -1
  311. package/dashboard/dist/assets/vendor-icons-Qk9qLjF_.js.map +0 -1
@@ -58,6 +58,7 @@ GET /api/escalations
58
58
  | `exists` | array of strings | Keys that must be present — `metadata ? key` |
59
59
  | `roles` | array of strings | Restrict to these roles (narrows within scope, never widens) |
60
60
  | `available` | `true`/`false` | `true` = unclaimed/expired only; `false` = held now |
61
+ | `jeopardy` | `1`/`true` | Only rows past their role's priority threshold — the exact predicate behind the Pace Board's priority count, so a jeopardy list's total equals the badge. Age from the role's `priority_facet` metadata timestamp (`created_at` when unset) against `priority_threshold_minutes` (`sla_minutes` when unset); undialed roles contribute no rows |
61
62
  | `orderBy` | array of `{field, direction?, numeric?}` | Sort by column or `metadata.<key>` |
62
63
 
63
64
  When any faceted parameter is present the request runs through the scoped faceted query.
@@ -204,7 +205,21 @@ Resolving an escalation starts a new workflow execution with the resolver's payl
204
205
  | `resolverPayload` | `object` | yes | The reviewer's decision — injected into `envelope.resolver` |
205
206
  | `metadata` | `object` | no | Outcome facets merged into the escalation's GIN-indexed metadata. Records *what happened* (disposition, timing) next to *what was asked*; `@>`-queryable. Distinct from `resolverPayload`, which resumes the workflow and is not indexed |
206
207
 
207
- The `resolverPayload` is stored exactly as submitted — the resolver payload is the payload. There is no server-side validation and no server-side mapping of its shape. The client forms the final payload: the React dashboard maps the flat form to the nested payload via each field's `x-lt-bind` before submitting.
208
+ The `resolverPayload` is stored exactly as submitted — the resolver payload is the payload, with no server-side mapping of its shape. The client forms the final payload: the React dashboard maps the flat form to the nested payload via each field's `x-lt-bind` before submitting.
209
+
210
+ **Schema enforcement (roles with `enforce_schema`):** every resolve surface validates the submitted `resolverPayload` against the escalation's resolved form schema (`metadata.form_schema` override, else the pinned `lt_role_schemas` snapshot, else the role's latest) and rejects violations with **422** before any state changes. The body is the canonical validation shape, identical across HTTP, SDK, MCP, and CLI — see [Schema Enforcement](../../schema-enforcement.md):
211
+
212
+ ```json
213
+ {
214
+ "error": "resolverPayload failed schema validation (1 violation)",
215
+ "code": "schema_validation",
216
+ "violations": [{ "field": "contact_email", "message": "Enter a valid email address" }],
217
+ "role": "intake-reviewer",
218
+ "schemaVersion": 3
219
+ }
220
+ ```
221
+
222
+ Bulk surfaces (`resolve-by-ids`, `resolve-all-or-none`) tag each violation with its `escalationId`, and one failing item blocks the batch before anything resolves.
208
223
 
209
224
  **Example request:**
210
225
 
@@ -268,6 +268,7 @@ Returns all roles with metadata and usage counts.
268
268
  | `metadata_schema` | `object \| null` | JSON Schema declaring the expected shape of `lt_escalations.metadata` for this role. Drives faceted-query key autocomplete and creation-time validation |
269
269
  | `properties` | `object` | Free user-owned bag — icons, colors, tags, etc. No reserved keys |
270
270
  | `ops_visible` | `boolean` | When `true`, the role appears as a station on the `/operations` view |
271
+ | `enforce_schema` | `boolean` | When `true`, every resolve surface validates submitted payloads against this role's form schema and rejects violations with `422` (`code: schema_validation`) — see [Schema Enforcement](../../schema-enforcement.md) |
271
272
  | `parent_role` | `string \| null` | Parent role in the process dependency graph; `null` for root stations |
272
273
  | `sla_minutes` | `number \| null` | Target resolution time in minutes (capacity setting) |
273
274
  | `target_per_hour` | `number \| null` | Intended throughput — items resolved per hour (capacity setting) |
@@ -366,6 +367,7 @@ When the update changes `form_schema` or `metadata_schema`, the new pair is snap
366
367
  | `metadata_schema` | `object \| null` | JSON Schema for `lt_escalations.metadata` |
367
368
  | `properties` | `object \| null` | Free user-owned bag |
368
369
  | `ops_visible` | `boolean` | Include in the `/operations` view |
370
+ | `enforce_schema` | `boolean` | Turn server-side resolver schema validation on/off |
369
371
  | `parent_role` | `string \| null` | Parent in the process dependency graph |
370
372
  | `sla_minutes` | `number \| null` | SLA target in minutes |
371
373
  | `target_per_hour` | `number \| null` | Throughput target (items per hour) |
@@ -373,6 +375,8 @@ When the update changes `form_schema` or `metadata_schema`, the new pair is snap
373
375
  | `priority_threshold_minutes` | `number \| null` | Priority age threshold in minutes; falls back to `sla_minutes` |
374
376
  | `priority_facet` | `string \| null` | Metadata key for the priority age origin (letters, numbers, underscores); falls back to `created_at` |
375
377
  | `upstream_roles` | `string[] \| null` | Replace the set of roles this station draws input from across other Operations sequences (omitted = preserve; `null` or `[]` = clear). Every entry must name an existing role other than this one. Distinct from `parent_role`, which places the role in its own sequence. |
378
+ | `list_schema` | `object \| null` | Rich formatting (x-lt-* markup) for this role's escalation LIST page. Versions independently of `form_schema`; the list always renders the latest |
379
+ | `default_pins` | `array \| null` | Pinned-view seeds for members: `[{ label, url, badge? }]` with dashboard-relative `url`s. Members promote, hide, or reorder them via their own preferences (`/api/me/preferences`). `null` clears |
376
380
  | `change_summary` | `string` | Label recorded on the schema version snapshot when this update changes a schema field |
377
381
 
378
382
  **Example request** — configure a role as a station in the ops view:
@@ -187,3 +187,42 @@ Deletes the user and all associated role assignments (cascade).
187
187
  ```json
188
188
  { "error": "User not found" }
189
189
  ```
190
+
191
+ ---
192
+
193
+ ## Self-Service — `/api/me`
194
+
195
+ Operations on the **authenticated caller** — no ids, no admin gates. Any signed-in user (or service account) may call them.
196
+
197
+ ### GET /api/me/preferences
198
+
199
+ The caller's preferences document — a generic per-user JSON store for presentation state (pinned views are the first tenant). Reads `{}` when unset.
200
+
201
+ **Response 200:**
202
+
203
+ ```json
204
+ {
205
+ "preferences": {
206
+ "pinnedViews": [
207
+ { "id": "pin-x1", "label": "Needs harvesting", "url": "/escalations/available?role=fleet-servicer&jeopardy=1&view=table", "badge": true }
208
+ ],
209
+ "hiddenRolePins": ["My machines"]
210
+ }
211
+ }
212
+ ```
213
+
214
+ ### PATCH /api/me/preferences
215
+
216
+ Shallow-merge the body into the caller's preferences: top-level keys overwrite whole, a `null` value deletes its key. The merge is a single guarded statement (no read-then-write) and the stored document is size-capped (~32 KB). Preferences carry presentation state only — URLs and UI choices, never data and never authorization.
217
+
218
+ **Request body** — any JSON object of preference keys:
219
+
220
+ ```json
221
+ { "pinnedViews": [ { "id": "pin-x1", "label": "Needs harvesting", "url": "/escalations/available?role=fleet-servicer&jeopardy=1", "badge": true } ], "theme": null }
222
+ ```
223
+
224
+ **Response 200:** the merged document, `{ "preferences": { ... } }`.
225
+
226
+ **Response 400:** body is not a JSON object. **Response 413:** the patch or the merged document would exceed the size cap (nothing is written).
227
+
228
+ See the [Pinned Views guide](../../hitl/pinned-views.md) for how the dashboard uses this store.
@@ -32,6 +32,9 @@ interface RoleDetail {
32
32
  worker_count: number | null;
33
33
  priority_threshold_minutes: number | null;
34
34
  priority_facet: string | null;
35
+ list_schema: Record<string, any> | null;
36
+ default_pins: { label: string; url: string; badge?: boolean }[] | null;
37
+ upstream_roles: string[];
35
38
  user_count: number;
36
39
  chain_count: number;
37
40
  workflow_count: number;
@@ -276,6 +279,8 @@ const result = await lt.roles.update({
276
279
  | `priority_threshold_minutes` | `number \| null` | No | Priority age threshold in minutes; falls back to `sla_minutes` |
277
280
  | `priority_facet` | `string \| null` | No | Metadata key for the priority age origin; falls back to `created_at` |
278
281
  | `upstream_roles` | `string[] \| null` | No | Replace the set of roles this station draws input from across other Operations sequences (omitted = preserve; `null` or `[]` = clear). Distinct from `parent_role`, which places the role in its own sequence |
282
+ | `list_schema` | `object \| null` | No | Rich formatting (x-lt-* markup) for this role's escalation LIST page; versions independently of `form_schema` |
283
+ | `default_pins` | `array \| null` | No | Pinned-view seeds for members: `[{ label, url, badge? }]` with dashboard-relative `url`s. Members promote, hide, or reorder via their own preferences (`lt.me`) |
279
284
  | `change_summary` | `string` | No | Label recorded on the schema version snapshot when this update changes a schema field |
280
285
 
281
286
  When the update changes `form_schema` or `metadata_schema`, the new pair is snapshotted into the role's version history and `current_schema_version` advances.
@@ -219,3 +219,42 @@ const result = await lt.users.removeRole({
219
219
  **Returns:** `LTApiResult<{ removed: true }>` -- returns 404 if role not found.
220
220
 
221
221
  **Auth:** Not required
222
+
223
+ ---
224
+
225
+ ## me — the authenticated caller
226
+
227
+ Self-service operations bound to the client's auth context (no ids, no admin gates).
228
+
229
+ ### me.getPreferences
230
+
231
+ ```typescript
232
+ const result = await lt.me.getPreferences();
233
+ // { status: 200, data: { preferences: { pinnedViews: [...] } } }
234
+ ```
235
+
236
+ **Returns:** `LTApiResult<{ preferences: Record<string, unknown> }>` — `{}` when unset.
237
+
238
+ ### me.patchPreferences
239
+
240
+ Shallow-merge a patch into the caller's preferences: top-level keys overwrite whole, `null` deletes a key. The merge is one guarded statement; the stored document is size-capped (~32 KB → 413).
241
+
242
+ ```typescript
243
+ const result = await lt.me.patchPreferences({
244
+ patch: {
245
+ pinnedViews: [
246
+ { id: 'pin-x1', label: 'Needs harvesting', url: '/escalations/available?role=fleet-servicer&jeopardy=1', badge: true },
247
+ ],
248
+ },
249
+ });
250
+ ```
251
+
252
+ **Parameters:**
253
+
254
+ | Field | Type | Required | Description |
255
+ |-------|------|----------|-------------|
256
+ | `patch` | `object` | Yes | Preference keys to merge; a `null` value deletes its key |
257
+
258
+ **Returns:** `LTApiResult<{ preferences: Record<string, unknown> }>` — the merged document.
259
+
260
+ **Auth:** Required (the operation targets the caller).
@@ -0,0 +1,45 @@
1
+ # Pinned Views
2
+
3
+ Task-queue personas live in a handful of exact queries — "pending harvest rows, oldest first," "everything about serial X." Every filter on the escalations list is already deep-linked in the URL (`role`, `status`, `facets`, `range`, `orderBy`, `view`, `jeopardy`); pinned views add persistence and placement: the right queries sit in the right person's nav from first login.
4
+
5
+ ---
6
+
7
+ ## User Preferences
8
+
9
+ Pins live in a per-user preferences store — a generic JSON document on the user record (pinned views are its first tenant, not its schema):
10
+
11
+ ```
12
+ GET /api/me/preferences → { preferences }
13
+ PATCH /api/me/preferences shallow top-level merge; null deletes a key
14
+ ```
15
+
16
+ The merge is a single guarded statement (no read-then-write) and the document is size-capped (~32 KB → 413). Preferences carry presentation state only — URLs and UI choices, never data and never authorization. A pin to a query the user cannot read simply renders that query's normal empty state; read-scope enforcement stays where it already lives.
17
+
18
+ ```json
19
+ {
20
+ "pinnedViews": [
21
+ { "id": "pin-x1", "label": "Needs harvesting", "url": "/escalations/available?role=fleet-servicer&facets=%7B%22machineState%22%3A%22finished%22%7D&view=table", "badge": true }
22
+ ],
23
+ "hiddenRolePins": ["My machines"]
24
+ }
25
+ ```
26
+
27
+ ## The Pinned Section
28
+
29
+ Pins render as a **Pinned** nav section: the user's own pins first, in stored order (drag to reorder, ✕ to remove), then role-provided defaults. **Pin this view** on the escalations list captures the live filter set into a new pin, prompting only for a label.
30
+
31
+ `badge: true` renders a live count beside the label using the same server-side predicate the pin opens onto (the pin's URL is parsed back into its query and counted with `limit: 1`) — so the badge and the list always agree, including `jeopardy=1` pins. Counts refresh on escalation events, and a pin whose URL isn't a countable escalations list renders without a badge.
32
+
33
+ ## Role Default Pins
34
+
35
+ A role may declare `default_pins: [{ label, url, badge? }]` (edited on Role Detail → Default Pins). Members see them in their Pinned section from first login, marked as role-provided, and may:
36
+
37
+ - **Promote** — copy into their own pins (an own pin with the same label supersedes the role default)
38
+ - **Hide** — dismiss it (recorded in `hiddenRolePins`)
39
+ - **Reorder** — promote first, then drag among their own pins
40
+
41
+ Membership *is* the persona's bookmark set — no per-user setup. Duplicate labels across a user's roles collapse to the first role's pin.
42
+
43
+ ## Reference Example
44
+
45
+ The `fleet-servicer` seed (`examples/seed-fleet-sim.ts`) ships a facet-board list schema, jeopardy dials, and three default pins — the board, a badged "Needs harvesting" facet query, and a badged jeopardy view — so the whole persona story is exercisable from the dashboard. The seeded `reviewer` user is a member.
@@ -12,11 +12,15 @@ Every string is a markdown/text template run through the same `{{domain.path}}`
12
12
 
13
13
  | Key | Level | Purpose |
14
14
  |-----|-------|---------|
15
- | `x-lt-layout` | schema | `"active-history"`, `"active"`, `"facet-table"`, or `"table"` |
15
+ | `x-lt-layout` | schema | `"active-history"`, `"active"`, `"facet-table"`, `"facet-board"`, or `"table"` |
16
16
  | `x-lt-help` | schema | Optional markdown header, interpolated with the active row |
17
17
  | `x-lt-active` | schema | The live item card: `{ title, subtitle?, body?, fields?: [{ label, value }] }` |
18
18
  | `x-lt-history` | schema | History column: `{ row: { title, subtitle?, meta? }, limit?, status? }` |
19
- | `x-lt-columns` | schema | Column definitions for `facet-table` layout: `[{ label: string, value: string }]` |
19
+ | `x-lt-columns` | schema | Column definitions for `facet-table` layout: `[{ label, value, format? }]` |
20
+ | `x-lt-group-by` | schema | `facet-board`: the `"domain.path"` whose value identifies each entity |
21
+ | `x-lt-card` | schema | `facet-board`: the per-entity card — `{ title, state?, fields?: [{ label, value, format? }] }` |
22
+
23
+ `format: "age"` on a `facet-table` column or `facet-board` field renders a timestamp as a compact age (`12m`, `3h`, `2d`) with the absolute time as its tooltip, repainted each minute — aging interim states are scannable at a glance.
20
24
 
21
25
  The **active** item is the first non-terminal escalation. The **history** column is lazy-loaded — a "Load full history" link fetches resolved items on demand (`status` defaults to `"resolved"`, `limit` to 25). Unknown or absent `x-lt-layout` is a safe no-op that falls back to the table.
22
26
 
@@ -73,3 +77,34 @@ Use when the queue contains many concurrent rows and the role's context is best
73
77
  ```
74
78
 
75
79
  A status dot precedes the first column automatically. ISO datetime values render as a readable relative date with a full-timestamp tooltip. Missing token values render as an em dash. Clicking any row navigates to the detail page. `x-lt-help` and `x-lt-active` are ignored in this layout.
80
+
81
+ ### `"facet-board"` — entity board
82
+
83
+ Use when the rows describe **entities** (machines, stations) rather than a queue: within the current filter scope, rows group by the resolved `x-lt-group-by` value and each group renders one card from its most recent row (by `created_at`). The board reflects the scope — groups with no matching rows simply don't render.
84
+
85
+ ```json
86
+ {
87
+ "x-lt-layout": "facet-board",
88
+ "x-lt-group-by": "metadata.fleetMachine",
89
+ "x-lt-card": {
90
+ "title": "{{metadata.fleetMachine}}",
91
+ "state": "{{metadata.machineState}}",
92
+ "fields": [
93
+ { "label": "PO", "value": "{{metadata.po}}" },
94
+ { "label": "Order", "value": "{{metadata.orderId}}" },
95
+ { "label": "Since", "value": "{{escalation.created_at}}", "format": "age" }
96
+ ]
97
+ }
98
+ }
99
+ ```
100
+
101
+ The card's `state` renders as a status chip (a stable hue per token — commonly a subtype or a metadata state facet). The grid wraps to the viewport (wall-screen friendly). `x-lt-help` renders above the board as in `facet-table`. In the digital-twin pattern — each machine advertising one live pending row — the board is exact by construction; for wider scopes it groups the fetched page, with standard pagination beyond it.
102
+
103
+ Cards are fully interactive (`x-lt-group-by` should be a `metadata.*` path so the facet affordances apply):
104
+
105
+ - **Click** opens the group's latest row in the detail view.
106
+ - **⇧ click** adds the entity's facet to the live filter set — additive, so shift-clicking narrows the board in place and the facet chips row shows each condition with its own remove.
107
+ - **Hovering the card footer** reveals the entity's history as the filtered **table** or **timeline** view (the same view modes the queue offers everywhere else).
108
+ - **Fields bound to a pure `{{metadata.KEY}}` token** carry the same hover pair the table's metadata cells offer: **filter** (narrows within the current role; ⇧ click adds to the current filters) and **search** (the facet across all roles).
109
+
110
+ The reference: `examples/seed-fleet-sim.ts` — one advert per machine, a `format: "age"` "Since" field, and role default pins (see [pinned-views.md](pinned-views.md)).
@@ -10,6 +10,17 @@ A field can be shown or hidden based on a value in the escalation context. Apply
10
10
 
11
11
  The value at `domain.path` is evaluated for truthiness. If truthy, the field shows; if absent, null, false, or an empty string, it is hidden. Prefix `!` to invert — show when the value is absent or falsy.
12
12
 
13
+ ### Equality forms
14
+
15
+ Compare the resolved value's string form against a literal:
16
+
17
+ ```json
18
+ "x-lt-showIf": "resolver.designatedStation=DRAFT"
19
+ "x-lt-showIf": "resolver.designatedStation!=DRAFT"
20
+ ```
21
+
22
+ The expected value is the raw remainder after the operator (trimmed; no quoting). Numbers and booleans compare via their string form (`metadata.count=3`, `metadata.live=true`). An absent value compares as the empty string — so `=X` is false and `!=X` is true when the path is missing. `resolver.` conditions react live as the user edits, exactly like the truthy form — this is what drives per-designation sub-surfaces on verdict forms (different fields for send-to-design vs send-to-printing).
23
+
13
24
  ### Domains
14
25
 
15
26
  | Domain | Resolves against |
@@ -10,6 +10,7 @@ For rich inputs beyond standard HTML types, set `x-lt-widget` on any field:
10
10
  | `"rich-text"` | Tall textarea for formatted text input. |
11
11
  | `"markdown"` | Markdown source rendered with headings, tables, lists, code blocks. Editable by default; set `readOnly: true` for a pure content block. |
12
12
  | `"checklist"` | Dynamic labeled checkboxes driven by runtime data. Items come from a `"domain.path"` declared in `x-lt-source`. |
13
+ | `"attachment"` | ReadOnly display of a captured binary (data URL or fetchable URL): images inline with click-to-full-size, everything else as a safe open/download link. `"image"` is an accepted alias. |
13
14
 
14
15
  ---
15
16
 
@@ -75,6 +76,27 @@ The submitted value is a PNG data URL of the canvas drawing.
75
76
 
76
77
  ---
77
78
 
79
+ ## Attachment
80
+
81
+ ReadOnly display of a binary captured earlier — typically by the `file-upload` widget on a prior form. The reporting form uploads the evidence (a defect photo, a signed PDF, a scan); the review form shows it:
82
+
83
+ ```json
84
+ {
85
+ "properties": {
86
+ "defect_evidence": {
87
+ "type": "string",
88
+ "readOnly": true,
89
+ "x-lt-widget": "attachment",
90
+ "description": "Uploaded by the reporting form"
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ The value is a data URL or a fetchable URL. Rendering dispatches on content type: `image/*` renders inline (constrained, click for full size); everything else — PDFs, scans, arbitrary binaries — renders as a labeled open/download affordance and is never embedded as a document (inlining arbitrary `data:` content is an XSS vector; the image context is script-inert). `"image"` is an accepted alias when the schema wants to name the constrained intent. An empty value reads as a quiet "No attachment".
97
+
98
+ ---
99
+
78
100
  ## Markdown
79
101
 
80
102
  Without `readOnly`, the field is a markdown editor — the resolver writes source in a Write/Preview toggle and the submitted value is the markdown text.
@@ -19,7 +19,7 @@ This works because the form is data: a JSON Schema stored on the role, versioned
19
19
  | Fields that appear based on another answer | `x-lt-showIf` | [x-lt-show-if.md](hitl/x-lt-show-if.md) |
20
20
  | Sections, columns, ordering, side-panel help | Layout keywords | [x-lt-layout.md](hitl/x-lt-layout.md) |
21
21
  | Runtime-driven items, files, signatures, SOP blocks | Widgets | [x-lt-widget.md](hitl/x-lt-widget.md) |
22
- | A role-authored list page for the whole queue | List schema | [x-lt-list-schema.md](hitl/x-lt-list-schema.md) |
22
+ | A role-authored list page document views, facet tables, entity card boards | List schema | [x-lt-list-schema.md](hitl/x-lt-list-schema.md) |
23
23
  | A fully custom UI nothing above can express | Iframe viewport | [iframe.md](hitl/iframe.md) |
24
24
 
25
25
  ---
@@ -62,6 +62,7 @@ When you author a HITL-backed workflow, the platform handles:
62
62
  - **Escalation chains** — users can re-route work to other roles
63
63
  - **AI triage** — optional auto-resolution for common patterns
64
64
  - **Credential security** — password fields use ephemeral tokens, never stored in plain text
65
+ - **Schema enforcement** — roles with `enforce_schema` validate every resolver payload server-side (dashboard, API, MCP, CLI alike) with the same pass the form runs, rejecting violations as a structured 422 (see [schema-enforcement.md](schema-enforcement.md))
65
66
  - **Telemetry** — trace IDs link escalations to OpenTelemetry traces
66
67
  - **Bulk operations** — bulk claim, assign, escalate, triage, and cancel for queue management
67
68
  - **Cancellation** — cancel pending escalations from the API or dashboard
@@ -82,10 +83,11 @@ Ordered as a learning path — each file adds one capability to the same form:
82
83
  | Conditional visibility (`x-lt-showIf`, `x-lt-hide-if-empty`) | [x-lt-show-if.md](hitl/x-lt-show-if.md) |
83
84
  | Layout, ordering, sections, binding, help panel | [x-lt-layout.md](hitl/x-lt-layout.md) |
84
85
  | Custom widgets (checklist, file upload, code editor, signature, markdown) | [x-lt-widget.md](hitl/x-lt-widget.md) |
85
- | List schema (`active-history`, `facet-table`) | [x-lt-list-schema.md](hitl/x-lt-list-schema.md) |
86
+ | List schema (`active-history`, `active`, `facet-table`, `facet-board` cards) | [x-lt-list-schema.md](hitl/x-lt-list-schema.md) |
86
87
  | Iframe viewport protocol | [iframe.md](hitl/iframe.md) |
87
88
  | Claim lifecycle, resolving from system code, outcome recording, cancellation | [resolution.md](hitl/resolution.md) |
88
89
  | Role routing, RBAC, scope, chains | [roles.md](hitl/roles.md) |
90
+ | Pinned views, user preferences, role default pins | [pinned-views.md](hitl/pinned-views.md) |
89
91
 
90
92
  ---
91
93
 
@@ -93,14 +95,14 @@ Ordered as a learning path — each file adds one capability to the same form:
93
95
 
94
96
  | Keyword | Level | Purpose |
95
97
  |---------|-------|---------|
96
- | `x-lt-widget` | field | Rich control: `file-upload`, `code-editor`, `signature`, `rich-text`, `markdown`, `checklist` |
98
+ | `x-lt-widget` | field | Rich control: `file-upload`, `code-editor`, `signature`, `rich-text`, `markdown`, `checklist`, `attachment` (alias `image`) |
97
99
  | `x-lt-source` | field | Data path for context-driven widgets: `"domain.path"` |
98
100
  | `x-lt-require-all` | field | Checklist completion guard — every item must be checked, except items declared `required: false` |
99
101
  | `x-lt-language` | field | Syntax hint for the `code-editor` widget |
100
102
  | `accept` | field | File-type filter for `file-upload` (e.g. `".pdf,.png"`) |
101
103
  | `x-lt-bind` | field | Path in the resolver payload (e.g. `"customer.email"`) |
102
104
  | `x-lt-span` | field | Column span in a `two-column` layout (`2` = full width) |
103
- | `x-lt-showIf` | field | Show field when a value is truthy at `domain.path`; prefix `!` to invert |
105
+ | `x-lt-showIf` | field | Show field when a value is truthy at `domain.path`; prefix `!` to invert; `=VALUE` / `!=VALUE` compare the string form |
104
106
  | `x-lt-hide-if-empty` | field | `true` — suppress the field when its value is null, `""`, `false`, or `0` |
105
107
  | `x-lt-section` | field | Section group label |
106
108
  | `x-lt-minimum` | field | Dynamic lower bound — resolves a `"domain.path"` from the escalation context |
@@ -109,7 +111,9 @@ Ordered as a learning path — each file adds one capability to the same form:
109
111
  | `x-lt-max-length` | field | Dynamic maximum string length — resolves a `"domain.path"` |
110
112
  | `x-lt-pattern-error` | field | Human-readable label for a `pattern` validation failure |
111
113
  | `x-lt-order` | schema | Field render sequence |
112
- | `x-lt-layout` | schema | `"two-column"` grid layout (form) or `"active-history"` / `"facet-table"` (list) |
114
+ | `x-lt-layout` | schema | `"two-column"` grid layout (form) or `"active-history"` / `"facet-table"` / `"facet-board"` (list) |
115
+ | `x-lt-group-by` | schema (list) | `facet-board`: the `"domain.path"` identifying each entity |
116
+ | `x-lt-card` | schema (list) | `facet-board`: per-entity card — `{ title, state?, fields? }`; fields accept `format: "age"` |
113
117
  | `x-lt-help` | schema | Markdown guidance for the side panel's Help view |
114
118
  | `x-lt-context` | schema | Plain-text fallback for the Help view when `x-lt-help` is absent |
115
119
  | `x-lt-viewport` | schema | Replace the generated form with a custom iframe UI |
@@ -0,0 +1,149 @@
1
+ # Resolver Schema Enforcement
2
+
3
+ A role's `form_schema` is an API contract, and the dashboard form is one client
4
+ of it. Escalations resolve from many directions — the dashboard, the SDK, the
5
+ CLI, webhooks that know a signal key, MCP agents, and simulated workforces —
6
+ and every one of them submits the same artifact: a resolver payload shaped by
7
+ the role's form schema and its `x-lt-bind` map. Schema enforcement makes the
8
+ server the authority on that contract: roles that opt in validate every
9
+ submitted payload at the API layer and reject violations before any state
10
+ changes.
11
+
12
+ ## The contract
13
+
14
+ A form schema declares, per field:
15
+
16
+ - **presence** — membership in `required`, honoring `x-lt-showIf` (a field the
17
+ submitter cannot see never blocks submission; conditions evaluate against
18
+ the escalation surface plus the submitted values as `resolver.*`)
19
+ - **type** — the declared JSON Schema `type`; a number field takes `1`, and
20
+ `"1"` is a violation
21
+ - **enum** — membership in the field's declared choices
22
+ - **bounds and patterns** — `minimum`/`maximum` (and their `x-lt-*` dynamic
23
+ variants resolved from the escalation context), lengths, `pattern`,
24
+ `format: email` / `format: uri`
25
+ - **checklist completion** — `x-lt-require-all` against the items resolved
26
+ from the row's own envelope
27
+
28
+ The pass that checks these is one isomorphic module
29
+ (`shared/form-validation/`): the dashboard runs it pre-submission on the flat
30
+ form values, and the API layer runs it on the submitted nested payload by
31
+ inverting each field's `x-lt-bind` path first. One implementation on both
32
+ sides means a payload that passes the client panel passes the server gate,
33
+ and a rejection lists exactly the errors the panel would show.
34
+
35
+ ## Opting a role in
36
+
37
+ Enforcement is per-role config: set `enforce_schema: true` on the role
38
+ (`PATCH /api/roles/:role`, the `update_role` MCP tool, or the dashboard role
39
+ editor). The schema validated against is resolved per escalation, most
40
+ specific first:
41
+
42
+ 1. `metadata.form_schema` — a full form embedded on the row
43
+ 2. the `lt_role_schemas` snapshot pinned by `metadata.schema_version`
44
+ 3. the role's live (latest) `form_schema`
45
+
46
+ A role that enforces but declares no schema has no contract to enforce, and
47
+ its resolves proceed unchanged.
48
+
49
+ ## The rejection shape
50
+
51
+ Every surface reports violations with one canonical body
52
+ (`types/validation.ts`), HTTP status 422:
53
+
54
+ ```json
55
+ {
56
+ "error": "resolverPayload failed schema validation (2 violations)",
57
+ "code": "schema_validation",
58
+ "violations": [
59
+ { "field": "contact_email", "message": "Enter a valid email address" },
60
+ { "field": "tier", "message": "Must be one of: free, starter, professional, enterprise" }
61
+ ],
62
+ "role": "intake-reviewer",
63
+ "schemaVersion": 3
64
+ }
65
+ ```
66
+
67
+ - **HTTP / routes** — the 422 response body
68
+ - **SDK** — `result.status === 422`, `result.code === 'schema_validation'`,
69
+ full body in `result.data`; narrow with `isValidationErrorBody(result.data)`
70
+ - **MCP** (`resolve_escalation`, `claim_and_resolve`) — the tool returns the
71
+ same JSON body with `isError: true`; `check_resolution` reports
72
+ `schema_enforced: true` on pending escalations whose role enforces
73
+ - **CLI** — prints each violation beneath the error line
74
+ - **Dashboard** — maps `violations` into the same errors panel the
75
+ pre-submission pass feeds
76
+
77
+ Bulk surfaces (`resolve-by-ids`, `resolve-all-or-none`) tag each violation
78
+ with its `escalationId`; all-or-none semantics extend to validation — one
79
+ failing item blocks the batch before anything resolves.
80
+
81
+ ## Enforcement points
82
+
83
+ All resolve surfaces run the same gate, before any resolution side effect:
84
+
85
+ | Surface | Gate placement |
86
+ |---|---|
87
+ | `POST /escalations/:id/resolve` | after RBAC and claim-liveness checks, before path dispatch |
88
+ | `POST /escalations/resolve-by-signal-key` | after write-scope check |
89
+ | `POST /escalations/resolve-by-ids` | after write-scope check; only rows in enforcing roles load in full |
90
+ | `POST /escalations/resolve-all-or-none` | after the unsupported-path check, before password redaction |
91
+ | `POST /escalations/resolve-by-metadata` | inside the atomic statement: an enforcing target returns `validation_required` with nothing written; the validated payload re-invokes with the row id asserted, and the second pass re-checks `pending` inside the same guarded statement |
92
+ | MCP `resolve_escalation` / `claim_and_resolve` | before the resolve; `claim_and_resolve` validates before claiming so a rejected payload never strands a claim |
93
+
94
+ ## Production cost model
95
+
96
+ Role schemas change on admin timescales; resolves happen on work timescales.
97
+ The gate is built so the hot path pays nothing for it:
98
+
99
+ - **The enforcing-role set** (`SELECT role FROM lt_roles WHERE enforce_schema`)
100
+ is cached under `ROLE_ENFORCEMENT_CACHE_TTL_MS` (default 30s). Every surface
101
+ consults the cached set first — with no enforcing roles involved, a resolve
102
+ adds zero SQL.
103
+ - **Pinned schema snapshots** are immutable and cache indefinitely (LRU-bounded).
104
+ - **Latest schemas** cache under the same TTL.
105
+ - In-process role writes invalidate immediately; across a container fleet,
106
+ staleness is bounded by one TTL window. Escalations that pin
107
+ `schema_version` are immune to latest-schema staleness.
108
+
109
+ Surfaces that already load the row (by id, by signal key, all-or-none)
110
+ validate with zero additional reads beyond the cached schema. Bulk-by-ids
111
+ shares its RBAC scope-row read with the gate and loads full rows only for
112
+ enforcing targets.
113
+
114
+ ## Typed consumption
115
+
116
+ Enforcement guarantees what entered the system; `parseResolverPayload`
117
+ (exported from the SDK) gives workflow code the consuming half. Declare the
118
+ payload shape once as a zod schema, derive the type from it, and parse
119
+ resolutions through it at the activity boundary:
120
+
121
+ ```typescript
122
+ import { z } from 'zod';
123
+ import { parseResolverPayload } from '@longtail/sdk';
124
+
125
+ export const IntakeResolverV1Schema = z.object({
126
+ customer: z.object({ name: z.string(), email: z.string() }),
127
+ contract: z.object({ tier: z.enum(['starter', 'professional']), approved: z.boolean() }),
128
+ });
129
+ export type IntakeResolverV1 = z.infer<typeof IntakeResolverV1Schema>;
130
+
131
+ const intake = parseResolverPayload(IntakeResolverV1Schema, response);
132
+ intake.customer.name; // typed and runtime-checked
133
+ ```
134
+
135
+ A non-conforming payload throws `ResolverPayloadTypeError` carrying the same
136
+ `{ field, message }` violation list, with the ZodError as `cause`. The
137
+ `rich-form` example (`examples/workflows/rich-form/`) shows the full pattern:
138
+ one zod schema as the single source of truth for the type, the runtime check,
139
+ and the version pin.
140
+
141
+ ## Rollout pattern
142
+
143
+ 1. Confirm the role's submitters send complete payloads — `check_resolution`
144
+ returns the `form_schema` (fields, types, `x-lt-bind` paths) an agent needs
145
+ to construct one, and flags `schema_enforced` once the role opts in.
146
+ 2. Flip `enforce_schema: true` on the role.
147
+ 3. Watch for 422s: each one identifies a submitter with a payload gap and
148
+ names the exact fields. The dashboard is unaffected — its pre-submission
149
+ pass is the same code.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",