@hotmeshio/long-tail 0.10.13 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/README.md +2 -4
  2. package/build/api/escalations/analytics.d.ts +6 -0
  3. package/build/api/escalations/analytics.js +123 -0
  4. package/build/api/escalations/index.d.ts +1 -0
  5. package/build/api/escalations/index.js +4 -1
  6. package/build/api/escalations/metadata.js +1 -1
  7. package/build/api/escalations/resolve.js +4 -4
  8. package/build/api/roles.d.ts +4 -0
  9. package/build/api/roles.js +8 -0
  10. package/build/api/scan-codes/choice.d.ts +11 -0
  11. package/build/api/scan-codes/choice.js +136 -0
  12. package/build/api/scan-codes/context.d.ts +24 -0
  13. package/build/api/scan-codes/context.js +86 -0
  14. package/build/api/scan-codes/execute.d.ts +1 -0
  15. package/build/api/scan-codes/execute.js +49 -244
  16. package/build/api/scan-codes/identity.d.ts +18 -0
  17. package/build/api/scan-codes/identity.js +116 -0
  18. package/build/api/scan-codes/index.d.ts +2 -0
  19. package/build/api/scan-codes/index.js +6 -1
  20. package/build/api/scan-codes/locate.d.ts +14 -0
  21. package/build/api/scan-codes/locate.js +127 -0
  22. package/build/api/scan-codes/present.d.ts +9 -0
  23. package/build/api/scan-codes/present.js +59 -0
  24. package/build/api/scan-codes/verbs.d.ts +15 -0
  25. package/build/api/scan-codes/verbs.js +205 -0
  26. package/build/bin/ltc.js +33 -0
  27. package/build/lib/cli/commands/escalations.d.ts +31 -0
  28. package/build/lib/cli/commands/escalations.js +88 -0
  29. package/build/lib/db/schemas/028_role_entity_facet.sql +18 -0
  30. package/build/lib/db/schemas/029_scan_scheme_kind.sql +31 -0
  31. package/build/modules/config.d.ts +5 -0
  32. package/build/modules/config.js +12 -0
  33. package/build/routes/escalations/acting.d.ts +12 -0
  34. package/build/routes/escalations/acting.js +25 -0
  35. package/build/routes/escalations/analytics.d.ts +8 -0
  36. package/build/routes/escalations/analytics.js +63 -0
  37. package/build/routes/escalations/index.js +3 -0
  38. package/build/routes/escalations/resolve.js +8 -1
  39. package/build/routes/escalations/single.js +13 -4
  40. package/build/routes/roles.js +1 -1
  41. package/build/routes/scan-codes.js +28 -2
  42. package/build/scripts/ortho-populate.js +14 -0
  43. package/build/scripts/printer-cycle.d.ts +25 -0
  44. package/build/scripts/printer-cycle.js +169 -0
  45. package/build/scripts/twin-farm.js +19 -5
  46. package/build/sdk/index.d.ts +2 -0
  47. package/build/sdk/index.js +3 -0
  48. package/build/services/escalation/aggregate-sql.d.ts +36 -0
  49. package/build/services/escalation/aggregate-sql.js +253 -0
  50. package/build/services/escalation/aggregate-validate.d.ts +57 -0
  51. package/build/services/escalation/aggregate-validate.js +218 -0
  52. package/build/services/escalation/aggregates.d.ts +26 -0
  53. package/build/services/escalation/aggregates.js +204 -0
  54. package/build/services/escalation/client.js +57 -21
  55. package/build/services/escalation/facet-sql.js +17 -0
  56. package/build/services/escalation/index.d.ts +2 -0
  57. package/build/services/escalation/index.js +8 -1
  58. package/build/services/escalation/metrics-cache.d.ts +18 -0
  59. package/build/services/escalation/metrics-cache.js +47 -1
  60. package/build/services/escalation/queries.js +2 -0
  61. package/build/services/escalation/resolver-validation.d.ts +1 -1
  62. package/build/services/escalation/resolver-validation.js +12 -5
  63. package/build/services/escalation/submit-guard.d.ts +29 -0
  64. package/build/services/escalation/submit-guard.js +57 -0
  65. package/build/services/iam/acting-identity.d.ts +16 -0
  66. package/build/services/iam/acting-identity.js +60 -0
  67. package/build/services/role/index.js +2 -0
  68. package/build/services/role/sql.d.ts +10 -2
  69. package/build/services/role/sql.js +14 -1
  70. package/build/services/role/types.d.ts +16 -0
  71. package/build/services/scan-code/read.js +4 -0
  72. package/build/services/scan-code/sql.d.ts +4 -4
  73. package/build/services/scan-code/sql.js +14 -8
  74. package/build/services/scan-code/validate.d.ts +2 -0
  75. package/build/services/scan-code/validate.js +86 -0
  76. package/build/services/scan-code/write.d.ts +4 -0
  77. package/build/services/scan-code/write.js +16 -2
  78. package/build/services/user/crud.d.ts +6 -0
  79. package/build/services/user/crud.js +16 -0
  80. package/build/services/user/index.d.ts +1 -1
  81. package/build/services/user/index.js +2 -1
  82. package/build/services/user/sql.d.ts +8 -0
  83. package/build/services/user/sql.js +9 -1
  84. package/build/shared/form-validation/index.d.ts +4 -0
  85. package/build/shared/form-validation/index.js +4 -0
  86. package/build/shared/form-validation/x-lt-labels.d.ts +28 -0
  87. package/build/shared/form-validation/x-lt-labels.js +42 -0
  88. package/build/shared/form-validation/x-lt-query.d.ts +56 -0
  89. package/build/shared/form-validation/x-lt-query.js +49 -0
  90. package/build/shared/form-validation/x-lt-submit-guard.d.ts +46 -0
  91. package/build/shared/form-validation/x-lt-submit-guard.js +25 -0
  92. package/build/shared/form-validation/x-lt-submit-on-claim.d.ts +22 -0
  93. package/build/shared/form-validation/x-lt-submit-on-claim.js +28 -0
  94. package/build/system/mcp-servers/admin/escalations.js +34 -0
  95. package/build/system/mcp-servers/admin/scan-codes.js +17 -1
  96. package/build/system/mcp-servers/admin/schemas.d.ts +643 -25
  97. package/build/system/mcp-servers/admin/schemas.js +127 -19
  98. package/build/system/mcp-servers/admin/users.js +3 -1
  99. package/build/system/mcp-servers/human-queue-schemas.d.ts +2 -2
  100. package/build/system/seed/tool-manifests-admin.d.ts +2283 -0
  101. package/build/system/seed/tool-manifests-admin.js +8 -5
  102. package/build/tsconfig.tsbuildinfo +1 -1
  103. package/build/types/analytics.d.ts +154 -0
  104. package/build/types/analytics.js +14 -0
  105. package/build/types/facets.d.ts +13 -0
  106. package/build/types/index.d.ts +5 -2
  107. package/build/types/index.js +5 -1
  108. package/build/types/scan-choice.d.ts +49 -0
  109. package/build/types/scan-choice.js +9 -0
  110. package/build/types/scan-code.d.ts +72 -1
  111. package/build/types/scan-code.js +30 -1
  112. package/dashboard/dist/assets/{AdminDashboard-Bv3pHBzn.js → AdminDashboard-DJ3LJQ09.js} +2 -2
  113. package/dashboard/dist/assets/{AdminDashboard-Bv3pHBzn.js.map → AdminDashboard-DJ3LJQ09.js.map} +1 -1
  114. package/dashboard/dist/assets/{AgentConfigPage-Dk2zlvDb.js → AgentConfigPage-BMMvaD0z.js} +2 -2
  115. package/dashboard/dist/assets/{AgentConfigPage-Dk2zlvDb.js.map → AgentConfigPage-BMMvaD0z.js.map} +1 -1
  116. package/dashboard/dist/assets/{AgentDetailPage-DUno6EGs.js → AgentDetailPage-C6wfsnRe.js} +2 -2
  117. package/dashboard/dist/assets/{AgentDetailPage-DUno6EGs.js.map → AgentDetailPage-C6wfsnRe.js.map} +1 -1
  118. package/dashboard/dist/assets/AgentsPage-Cwqka7nE.js +2 -0
  119. package/dashboard/dist/assets/{AgentsPage-CToVu1qj.js.map → AgentsPage-Cwqka7nE.js.map} +1 -1
  120. package/dashboard/dist/assets/AvailableEscalationsPage-CVlG_AVC.js +2 -0
  121. package/dashboard/dist/assets/AvailableEscalationsPage-CVlG_AVC.js.map +1 -0
  122. package/dashboard/dist/assets/{BotPicker-DQOpVB4M.js → BotPicker-BBkJBXZB.js} +2 -2
  123. package/dashboard/dist/assets/{BotPicker-DQOpVB4M.js.map → BotPicker-BBkJBXZB.js.map} +1 -1
  124. package/dashboard/dist/assets/{CapabilitiesPage-Dmf46PUJ.js → CapabilitiesPage-wxmE4917.js} +2 -2
  125. package/dashboard/dist/assets/{CapabilitiesPage-Dmf46PUJ.js.map → CapabilitiesPage-wxmE4917.js.map} +1 -1
  126. package/dashboard/dist/assets/{CollapsibleSection-CDADefUY.js → CollapsibleSection-B3FsrHwH.js} +2 -2
  127. package/dashboard/dist/assets/{CollapsibleSection-CDADefUY.js.map → CollapsibleSection-B3FsrHwH.js.map} +1 -1
  128. package/dashboard/dist/assets/CountdownTimer-B3nCqJdC.js +2 -0
  129. package/dashboard/dist/assets/CountdownTimer-B3nCqJdC.js.map +1 -0
  130. package/dashboard/dist/assets/{CredentialsPage-8LIEvomi.js → CredentialsPage-Bv875IHe.js} +2 -2
  131. package/dashboard/dist/assets/{CredentialsPage-8LIEvomi.js.map → CredentialsPage-Bv875IHe.js.map} +1 -1
  132. package/dashboard/dist/assets/{CronLabel-CK3WSsM5.js → CronLabel-CSzjWuwj.js} +2 -2
  133. package/dashboard/dist/assets/{CronLabel-CK3WSsM5.js.map → CronLabel-CSzjWuwj.js.map} +1 -1
  134. package/dashboard/dist/assets/{CustomDurationPicker-FPmlA6cE.js → CustomDurationPicker-CCpqf3e4.js} +2 -2
  135. package/dashboard/dist/assets/{CustomDurationPicker-FPmlA6cE.js.map → CustomDurationPicker-CCpqf3e4.js.map} +1 -1
  136. package/dashboard/dist/assets/{DropZone-CL9gA7pu.js → DropZone-CJkk-U3o.js} +2 -2
  137. package/dashboard/dist/assets/{DropZone-CL9gA7pu.js.map → DropZone-CJkk-U3o.js.map} +1 -1
  138. package/dashboard/dist/assets/{ElapsedCell-BZVkVxE0.js → ElapsedCell-WV6pUaKo.js} +2 -2
  139. package/dashboard/dist/assets/{ElapsedCell-BZVkVxE0.js.map → ElapsedCell-WV6pUaKo.js.map} +1 -1
  140. package/dashboard/dist/assets/EntityTimelinePanel-DrL86Yrf.js +2 -0
  141. package/dashboard/dist/assets/EntityTimelinePanel-DrL86Yrf.js.map +1 -0
  142. package/dashboard/dist/assets/{EscalationListSchemaPage-kYLdUtZX.js → EscalationListSchemaPage-CTQCALw4.js} +3 -3
  143. package/dashboard/dist/assets/{EscalationListSchemaPage-kYLdUtZX.js.map → EscalationListSchemaPage-CTQCALw4.js.map} +1 -1
  144. package/dashboard/dist/assets/{EscalationSchemaPage-kUmCDivo.js → EscalationSchemaPage-D_UgP0pv.js} +3 -3
  145. package/dashboard/dist/assets/{EscalationSchemaPage-kUmCDivo.js.map → EscalationSchemaPage-D_UgP0pv.js.map} +1 -1
  146. package/dashboard/dist/assets/{EscalationsOverview-D17auO-m.js → EscalationsOverview-DuIUyxr0.js} +2 -2
  147. package/dashboard/dist/assets/{EscalationsOverview-D17auO-m.js.map → EscalationsOverview-DuIUyxr0.js.map} +1 -1
  148. package/dashboard/dist/assets/EventTable-BV6unphX.js +2 -0
  149. package/dashboard/dist/assets/EventTable-BV6unphX.js.map +1 -0
  150. package/dashboard/dist/assets/FilterBar-DYFgzXNB.js +2 -0
  151. package/dashboard/dist/assets/FilterBar-DYFgzXNB.js.map +1 -0
  152. package/dashboard/dist/assets/{GraphInvokePage-D11uOnZi.js → GraphInvokePage-BGHVUipJ.js} +2 -2
  153. package/dashboard/dist/assets/{GraphInvokePage-D11uOnZi.js.map → GraphInvokePage-BGHVUipJ.js.map} +1 -1
  154. package/dashboard/dist/assets/HomePage-B-S2cK3E.js +2 -0
  155. package/dashboard/dist/assets/{HomePage-Bcq10Bxx.js.map → HomePage-B-S2cK3E.js.map} +1 -1
  156. package/dashboard/dist/assets/ListToolbar-Bl7F3A28.js +2 -0
  157. package/dashboard/dist/assets/{ListToolbar-IPXsTb3Q.js.map → ListToolbar-Bl7F3A28.js.map} +1 -1
  158. package/dashboard/dist/assets/{McpOverview-Dx2j0KkF.js → McpOverview-_T9IsCD_.js} +2 -2
  159. package/dashboard/dist/assets/{McpOverview-Dx2j0KkF.js.map → McpOverview-_T9IsCD_.js.map} +1 -1
  160. package/dashboard/dist/assets/{McpQueryDetailPage-CzF_bna8.js → McpQueryDetailPage-DtsVqKPw.js} +2 -2
  161. package/dashboard/dist/assets/{McpQueryDetailPage-CzF_bna8.js.map → McpQueryDetailPage-DtsVqKPw.js.map} +1 -1
  162. package/dashboard/dist/assets/McpQueryPage-DX5u-1bx.js +2 -0
  163. package/dashboard/dist/assets/{McpQueryPage-CNsuiw_q.js.map → McpQueryPage-DX5u-1bx.js.map} +1 -1
  164. package/dashboard/dist/assets/McpRunDetailPage-BhcqqtYS.js +2 -0
  165. package/dashboard/dist/assets/McpRunDetailPage-BhcqqtYS.js.map +1 -0
  166. package/dashboard/dist/assets/{McpRunsPage-CK96s3Nt.js → McpRunsPage-L-1y8wnX.js} +2 -2
  167. package/dashboard/dist/assets/{McpRunsPage-CK96s3Nt.js.map → McpRunsPage-L-1y8wnX.js.map} +1 -1
  168. package/dashboard/dist/assets/{NamespacePill-B9Vc_Pz2.js → NamespacePill-in1SLd0W.js} +2 -2
  169. package/dashboard/dist/assets/{NamespacePill-B9Vc_Pz2.js.map → NamespacePill-in1SLd0W.js.map} +1 -1
  170. package/dashboard/dist/assets/OperationsPage-i8KcDpKo.js +3 -0
  171. package/dashboard/dist/assets/OperationsPage-i8KcDpKo.js.map +1 -0
  172. package/dashboard/dist/assets/OperatorDashboard-CIbDzOlh.js +2 -0
  173. package/dashboard/dist/assets/{OperatorDashboard-C-jE0IRA.js.map → OperatorDashboard-CIbDzOlh.js.map} +1 -1
  174. package/dashboard/dist/assets/{PageHeader-Ce4GcIYo.js → PageHeader-BJgWMZtw.js} +2 -2
  175. package/dashboard/dist/assets/{PageHeader-Ce4GcIYo.js.map → PageHeader-BJgWMZtw.js.map} +1 -1
  176. package/dashboard/dist/assets/{PageHeaderWithStats-Ck0KZvXD.js → PageHeaderWithStats-Ccio4vyG.js} +2 -2
  177. package/dashboard/dist/assets/{PageHeaderWithStats-Ck0KZvXD.js.map → PageHeaderWithStats-Ccio4vyG.js.map} +1 -1
  178. package/dashboard/dist/assets/{PersonaDetailPage-CJ6jqo7h.js → PersonaDetailPage-BOaf0TW1.js} +2 -2
  179. package/dashboard/dist/assets/{PersonaDetailPage-CJ6jqo7h.js.map → PersonaDetailPage-BOaf0TW1.js.map} +1 -1
  180. package/dashboard/dist/assets/{PersonasPage-D8hL3kL5.js → PersonasPage-DSdRb90f.js} +2 -2
  181. package/dashboard/dist/assets/{PersonasPage-D8hL3kL5.js.map → PersonasPage-DSdRb90f.js.map} +1 -1
  182. package/dashboard/dist/assets/{ProcessDetailPage-Vnx8Z5_y.js → ProcessDetailPage-DN9NqfeP.js} +2 -2
  183. package/dashboard/dist/assets/{ProcessDetailPage-Vnx8Z5_y.js.map → ProcessDetailPage-DN9NqfeP.js.map} +1 -1
  184. package/dashboard/dist/assets/{ProcessesListPage-DRYtvS3N.js → ProcessesListPage-CfQEWaD4.js} +2 -2
  185. package/dashboard/dist/assets/{ProcessesListPage-DRYtvS3N.js.map → ProcessesListPage-CfQEWaD4.js.map} +1 -1
  186. package/dashboard/dist/assets/RoleDetailPage-BC5HL7ls.js +8 -0
  187. package/dashboard/dist/assets/RoleDetailPage-BC5HL7ls.js.map +1 -0
  188. package/dashboard/dist/assets/{RolePill-CnuBPhPw.js → RolePill-BNAP-uK6.js} +2 -2
  189. package/dashboard/dist/assets/{RolePill-CnuBPhPw.js.map → RolePill-BNAP-uK6.js.map} +1 -1
  190. package/dashboard/dist/assets/RolesPage-DCnW0VmD.js +2 -0
  191. package/dashboard/dist/assets/RolesPage-DCnW0VmD.js.map +1 -0
  192. package/dashboard/dist/assets/RunAsSelector-twbuX7fB.js +2 -0
  193. package/dashboard/dist/assets/{RunAsSelector-CS3KQ-BF.js.map → RunAsSelector-twbuX7fB.js.map} +1 -1
  194. package/dashboard/dist/assets/ScanCodesPage-CPNg6K--.js +2 -0
  195. package/dashboard/dist/assets/ScanCodesPage-CPNg6K--.js.map +1 -0
  196. package/dashboard/dist/assets/ScanSchemeDetailPage-DyLmCamO.js +2 -0
  197. package/dashboard/dist/assets/ScanSchemeDetailPage-DyLmCamO.js.map +1 -0
  198. package/dashboard/dist/assets/ScanStationPage-DoZccU5B.js +2 -0
  199. package/dashboard/dist/assets/ScanStationPage-DoZccU5B.js.map +1 -0
  200. package/dashboard/dist/assets/{ScopeBadge-BUpYpS3x.js → ScopeBadge-BG7KkZ8Q.js} +2 -2
  201. package/dashboard/dist/assets/{ScopeBadge-BUpYpS3x.js.map → ScopeBadge-BG7KkZ8Q.js.map} +1 -1
  202. package/dashboard/dist/assets/SegmentedTabs-DXNzY05q.js +2 -0
  203. package/dashboard/dist/assets/SegmentedTabs-DXNzY05q.js.map +1 -0
  204. package/dashboard/dist/assets/StickyPagination-DIeYMPPd.js +2 -0
  205. package/dashboard/dist/assets/StickyPagination-DIeYMPPd.js.map +1 -0
  206. package/dashboard/dist/assets/{StreamMessageDetail-DyJyZwBl.js → StreamMessageDetail-gU4CUeQI.js} +2 -2
  207. package/dashboard/dist/assets/{StreamMessageDetail-DyJyZwBl.js.map → StreamMessageDetail-gU4CUeQI.js.map} +1 -1
  208. package/dashboard/dist/assets/{SwimlaneTimeline-BlzV74V5.js → SwimlaneTimeline-Br-nBA4l.js} +2 -2
  209. package/dashboard/dist/assets/{SwimlaneTimeline-BlzV74V5.js.map → SwimlaneTimeline-Br-nBA4l.js.map} +1 -1
  210. package/dashboard/dist/assets/{TagInput-BbZ25nNe.js → TagInput-CvM5n6ww.js} +2 -2
  211. package/dashboard/dist/assets/{TagInput-BbZ25nNe.js.map → TagInput-CvM5n6ww.js.map} +1 -1
  212. package/dashboard/dist/assets/{TaskDetailPage-B0p_Z4J_.js → TaskDetailPage-_9cr_R0u.js} +2 -2
  213. package/dashboard/dist/assets/{TaskDetailPage-B0p_Z4J_.js.map → TaskDetailPage-_9cr_R0u.js.map} +1 -1
  214. package/dashboard/dist/assets/{TaskQueuePill-D0QKBrN3.js → TaskQueuePill-D4JPHXOq.js} +2 -2
  215. package/dashboard/dist/assets/{TaskQueuePill-D0QKBrN3.js.map → TaskQueuePill-D4JPHXOq.js.map} +1 -1
  216. package/dashboard/dist/assets/{TasksListPage-CiGR2QiQ.js → TasksListPage-DVENC9Jp.js} +2 -2
  217. package/dashboard/dist/assets/{TasksListPage-CiGR2QiQ.js.map → TasksListPage-DVENC9Jp.js.map} +1 -1
  218. package/dashboard/dist/assets/{TimeAgo-D_DA9ucC.js → TimeAgo-C9yYnOui.js} +2 -2
  219. package/dashboard/dist/assets/{TimeAgo-D_DA9ucC.js.map → TimeAgo-C9yYnOui.js.map} +1 -1
  220. package/dashboard/dist/assets/{TimestampCell-C7rkC9nh.js → TimestampCell-DoB8cYgw.js} +2 -2
  221. package/dashboard/dist/assets/{TimestampCell-C7rkC9nh.js.map → TimestampCell-DoB8cYgw.js.map} +1 -1
  222. package/dashboard/dist/assets/{ToolPill-8xY9rkoc.js → ToolPill-BUjbC-Tj.js} +2 -2
  223. package/dashboard/dist/assets/{ToolPill-8xY9rkoc.js.map → ToolPill-BUjbC-Tj.js.map} +1 -1
  224. package/dashboard/dist/assets/{ToolTestPanel-E8efYGYs.js → ToolTestPanel-CBtsIP8K.js} +2 -2
  225. package/dashboard/dist/assets/{ToolTestPanel-E8efYGYs.js.map → ToolTestPanel-CBtsIP8K.js.map} +1 -1
  226. package/dashboard/dist/assets/{TopicDetailPage-B455XrRc.js → TopicDetailPage-mR4Jaz83.js} +4 -4
  227. package/dashboard/dist/assets/{TopicDetailPage-B455XrRc.js.map → TopicDetailPage-mR4Jaz83.js.map} +1 -1
  228. package/dashboard/dist/assets/TopicsPage-CP7HFxIC.js +2 -0
  229. package/dashboard/dist/assets/{TopicsPage-1VAc29Qw.js.map → TopicsPage-CP7HFxIC.js.map} +1 -1
  230. package/dashboard/dist/assets/{UserName-DikzjQHJ.js → UserName-BnLF6yhz.js} +2 -2
  231. package/dashboard/dist/assets/{UserName-DikzjQHJ.js.map → UserName-BnLF6yhz.js.map} +1 -1
  232. package/dashboard/dist/assets/WorkflowExecutionPage-xL956RZi.js +2 -0
  233. package/dashboard/dist/assets/WorkflowExecutionPage-xL956RZi.js.map +1 -0
  234. package/dashboard/dist/assets/{WorkflowPill-Da406nEj.js → WorkflowPill-tXU0-eXu.js} +2 -2
  235. package/dashboard/dist/assets/{WorkflowPill-Da406nEj.js.map → WorkflowPill-tXU0-eXu.js.map} +1 -1
  236. package/dashboard/dist/assets/WorkflowsDashboard-B-u7zFKM.js +2 -0
  237. package/dashboard/dist/assets/{WorkflowsDashboard-BMQd_0tW.js.map → WorkflowsDashboard-B-u7zFKM.js.map} +1 -1
  238. package/dashboard/dist/assets/{WorkflowsOverview-CdxjVifp.js → WorkflowsOverview-CqZVarK-.js} +2 -2
  239. package/dashboard/dist/assets/{WorkflowsOverview-CdxjVifp.js.map → WorkflowsOverview-CqZVarK-.js.map} +1 -1
  240. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DNVffIXa.js → YamlWorkflowDetailPage-DiFLavW0.js} +10 -10
  241. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DNVffIXa.js.map → YamlWorkflowDetailPage-DiFLavW0.js.map} +1 -1
  242. package/dashboard/dist/assets/{YamlWorkflowsPage-BGP8wuos.js → YamlWorkflowsPage-BuQM6uZA.js} +2 -2
  243. package/dashboard/dist/assets/{YamlWorkflowsPage-BGP8wuos.js.map → YamlWorkflowsPage-BuQM6uZA.js.map} +1 -1
  244. package/dashboard/dist/assets/{agents-BZJLIJ4h.js → agents-DU9GPovp.js} +2 -2
  245. package/dashboard/dist/assets/{agents-BZJLIJ4h.js.map → agents-DU9GPovp.js.map} +1 -1
  246. package/dashboard/dist/assets/{bots-ClLcDGJb.js → bots-Cy-Hmg7w.js} +2 -2
  247. package/dashboard/dist/assets/{bots-ClLcDGJb.js.map → bots-Cy-Hmg7w.js.map} +1 -1
  248. package/dashboard/dist/assets/capabilities-sSJJqrp-.js +2 -0
  249. package/dashboard/dist/assets/{capabilities-IaCcS7ge.js.map → capabilities-sSJJqrp-.js.map} +1 -1
  250. package/dashboard/dist/assets/{controlplane-Zi4m_MSx.js → controlplane-TVCjDHQ-.js} +2 -2
  251. package/dashboard/dist/assets/{controlplane-Zi4m_MSx.js.map → controlplane-TVCjDHQ-.js.map} +1 -1
  252. package/dashboard/dist/assets/escalation-columns-BpkjKYGd.js +2 -0
  253. package/dashboard/dist/assets/escalation-columns-BpkjKYGd.js.map +1 -0
  254. package/dashboard/dist/assets/index-5TblaCXQ.css +1 -0
  255. package/dashboard/dist/assets/index-7BP6FaCZ.js +2 -0
  256. package/dashboard/dist/assets/{index-Dldi3Q3M.js.map → index-7BP6FaCZ.js.map} +1 -1
  257. package/dashboard/dist/assets/{index-BzkE48xJ.js → index-B7IaBPBg.js} +4 -4
  258. package/dashboard/dist/assets/{index-BzkE48xJ.js.map → index-B7IaBPBg.js.map} +1 -1
  259. package/dashboard/dist/assets/index-BE0phFw0.js +2 -0
  260. package/dashboard/dist/assets/{index-CLmZaNTK.js.map → index-BE0phFw0.js.map} +1 -1
  261. package/dashboard/dist/assets/{index-BqGnhkh0.js → index-BYNN9N1T.js} +2 -2
  262. package/dashboard/dist/assets/{index-BqGnhkh0.js.map → index-BYNN9N1T.js.map} +1 -1
  263. package/dashboard/dist/assets/{index-kiKZresb.js → index-CLPVqbVY.js} +2 -2
  264. package/dashboard/dist/assets/{index-kiKZresb.js.map → index-CLPVqbVY.js.map} +1 -1
  265. package/dashboard/dist/assets/{index-C3o4OisO.js → index-Cfv86_L9.js} +2 -2
  266. package/dashboard/dist/assets/{index-C3o4OisO.js.map → index-Cfv86_L9.js.map} +1 -1
  267. package/dashboard/dist/assets/{index-yGNt29V9.js → index-CtwbQ_-m.js} +2 -2
  268. package/dashboard/dist/assets/{index-yGNt29V9.js.map → index-CtwbQ_-m.js.map} +1 -1
  269. package/dashboard/dist/assets/index-DOai950R.js +3 -0
  270. package/dashboard/dist/assets/index-DOai950R.js.map +1 -0
  271. package/dashboard/dist/assets/index-DTtHNC-D.js +5 -0
  272. package/dashboard/dist/assets/{index-CvNBdkB1.js.map → index-DTtHNC-D.js.map} +1 -1
  273. package/dashboard/dist/assets/{index-CJu2PBNx.js → index-DsWnVyVN.js} +2 -2
  274. package/dashboard/dist/assets/{index-CJu2PBNx.js.map → index-DsWnVyVN.js.map} +1 -1
  275. package/dashboard/dist/assets/{index-D0zFReGL.js → index-Dy-MMYti.js} +2 -2
  276. package/dashboard/dist/assets/{index-D0zFReGL.js.map → index-Dy-MMYti.js.map} +1 -1
  277. package/dashboard/dist/assets/index-errgQT5x.js +65 -0
  278. package/dashboard/dist/assets/index-errgQT5x.js.map +1 -0
  279. package/dashboard/dist/assets/{index-CmnNqpWg.js → index-fgBDhnI6.js} +2 -2
  280. package/dashboard/dist/assets/{index-CmnNqpWg.js.map → index-fgBDhnI6.js.map} +1 -1
  281. package/dashboard/dist/assets/{knowledge-DWLxGTib.js → knowledge-arh4NfmL.js} +2 -2
  282. package/dashboard/dist/assets/{knowledge-DWLxGTib.js.map → knowledge-arh4NfmL.js.map} +1 -1
  283. package/dashboard/dist/assets/{mcp-B6AARZ2I.js → mcp-QGf5jHCo.js} +2 -2
  284. package/dashboard/dist/assets/{mcp-B6AARZ2I.js.map → mcp-QGf5jHCo.js.map} +1 -1
  285. package/dashboard/dist/assets/{mcp-query-DrkvBU3a.js → mcp-query-BEJN1nMZ.js} +2 -2
  286. package/dashboard/dist/assets/{mcp-query-DrkvBU3a.js.map → mcp-query-BEJN1nMZ.js.map} +1 -1
  287. package/dashboard/dist/assets/{personas-Cmz2SAEU.js → personas-DYtw1COf.js} +2 -2
  288. package/dashboard/dist/assets/{personas-Cmz2SAEU.js.map → personas-DYtw1COf.js.map} +1 -1
  289. package/dashboard/dist/assets/{pipelines-CSgfVCMy.js → pipelines-Byi6PnbE.js} +2 -2
  290. package/dashboard/dist/assets/{pipelines-CSgfVCMy.js.map → pipelines-Byi6PnbE.js.map} +1 -1
  291. package/dashboard/dist/assets/{tasks-UOtUkDk-.js → tasks-D0SQdENz.js} +2 -2
  292. package/dashboard/dist/assets/{tasks-UOtUkDk-.js.map → tasks-D0SQdENz.js.map} +1 -1
  293. package/dashboard/dist/assets/{topics-Df5hGh6o.js → topics-DgDv5ZXP.js} +2 -2
  294. package/dashboard/dist/assets/{topics-Df5hGh6o.js.map → topics-DgDv5ZXP.js.map} +1 -1
  295. package/dashboard/dist/assets/useEventHooks-DZaYzE7s.js +2 -0
  296. package/dashboard/dist/assets/{useEventHooks-PDbuSJ12.js.map → useEventHooks-DZaYzE7s.js.map} +1 -1
  297. package/dashboard/dist/assets/{useNamespace-tZSOlge3.js → useNamespace-C1OqiNXN.js} +2 -2
  298. package/dashboard/dist/assets/{useNamespace-tZSOlge3.js.map → useNamespace-C1OqiNXN.js.map} +1 -1
  299. package/dashboard/dist/assets/{useYamlActivityEvents-CINLzE32.js → useYamlActivityEvents-CkmXDm-Z.js} +2 -2
  300. package/dashboard/dist/assets/{useYamlActivityEvents-CINLzE32.js.map → useYamlActivityEvents-CkmXDm-Z.js.map} +1 -1
  301. package/dashboard/dist/assets/{users-G8qwyZKS.js → users-D4DCFPH1.js} +2 -2
  302. package/dashboard/dist/assets/{users-G8qwyZKS.js.map → users-D4DCFPH1.js.map} +1 -1
  303. package/dashboard/dist/assets/{vendor-icons-BpZiWH2f.js → vendor-icons-BxuyFxH2.js} +156 -136
  304. package/dashboard/dist/assets/vendor-icons-BxuyFxH2.js.map +1 -0
  305. package/dashboard/dist/assets/{workflows-DbPqi1TW.js → workflows-qyc0ku_5.js} +2 -2
  306. package/dashboard/dist/assets/{workflows-DbPqi1TW.js.map → workflows-qyc0ku_5.js.map} +1 -1
  307. package/dashboard/dist/assets/{yaml-workflows-DozD8hpR.js → yaml-workflows-DWFww9Xn.js} +2 -2
  308. package/dashboard/dist/assets/{yaml-workflows-DozD8hpR.js.map → yaml-workflows-DWFww9Xn.js.map} +1 -1
  309. package/dashboard/dist/index.html +3 -3
  310. package/docs/api/http/escalations.md +133 -0
  311. package/docs/api/http/roles.md +6 -0
  312. package/docs/api/mcp/admin.md +53 -0
  313. package/docs/api/sdk/escalations.md +58 -0
  314. package/docs/cli.md +11 -0
  315. package/docs/dashboard.md +22 -20
  316. package/docs/data.md +12 -4
  317. package/docs/design-principles.md +9 -7
  318. package/docs/escalation-analytics.md +251 -0
  319. package/docs/hitl/roles.md +1 -1
  320. package/docs/hitl/x-lt-embed.md +8 -4
  321. package/docs/hitl/x-lt-footer.md +67 -0
  322. package/docs/hitl/x-lt-list-schema.md +10 -3
  323. package/docs/hitl-guide.md +11 -2
  324. package/docs/iam.md +12 -0
  325. package/docs/operations.md +17 -0
  326. package/docs/scan-codes.md +112 -0
  327. package/docs/sdk.md +1 -1
  328. package/package.json +2 -1
  329. package/dashboard/dist/assets/AgentsPage-CToVu1qj.js +0 -2
  330. package/dashboard/dist/assets/AvailableEscalationsPage-DldboL4W.js +0 -2
  331. package/dashboard/dist/assets/AvailableEscalationsPage-DldboL4W.js.map +0 -1
  332. package/dashboard/dist/assets/CountdownTimer-DPjIkerB.js +0 -2
  333. package/dashboard/dist/assets/CountdownTimer-DPjIkerB.js.map +0 -1
  334. package/dashboard/dist/assets/EventTable-CwPWl163.js +0 -2
  335. package/dashboard/dist/assets/EventTable-CwPWl163.js.map +0 -1
  336. package/dashboard/dist/assets/FilterBar-D7GghUsU.js +0 -2
  337. package/dashboard/dist/assets/FilterBar-D7GghUsU.js.map +0 -1
  338. package/dashboard/dist/assets/HomePage-Bcq10Bxx.js +0 -2
  339. package/dashboard/dist/assets/ListToolbar-IPXsTb3Q.js +0 -2
  340. package/dashboard/dist/assets/McpQueryPage-CNsuiw_q.js +0 -2
  341. package/dashboard/dist/assets/McpRunDetailPage-BJguEMS0.js +0 -2
  342. package/dashboard/dist/assets/McpRunDetailPage-BJguEMS0.js.map +0 -1
  343. package/dashboard/dist/assets/OperationsPage-DvWrLuH6.js +0 -2
  344. package/dashboard/dist/assets/OperationsPage-DvWrLuH6.js.map +0 -1
  345. package/dashboard/dist/assets/OperatorDashboard-C-jE0IRA.js +0 -2
  346. package/dashboard/dist/assets/RoleDetailPage-BKkUvTO-.js +0 -8
  347. package/dashboard/dist/assets/RoleDetailPage-BKkUvTO-.js.map +0 -1
  348. package/dashboard/dist/assets/RolesPage-BHzWK9n6.js +0 -2
  349. package/dashboard/dist/assets/RolesPage-BHzWK9n6.js.map +0 -1
  350. package/dashboard/dist/assets/RunAsSelector-CS3KQ-BF.js +0 -2
  351. package/dashboard/dist/assets/ScanCodesPage-BW4P4zWf.js +0 -2
  352. package/dashboard/dist/assets/ScanCodesPage-BW4P4zWf.js.map +0 -1
  353. package/dashboard/dist/assets/ScanSchemeDetailPage-BLykA4j-.js +0 -2
  354. package/dashboard/dist/assets/ScanSchemeDetailPage-BLykA4j-.js.map +0 -1
  355. package/dashboard/dist/assets/StickyPagination-hMrnLXde.js +0 -2
  356. package/dashboard/dist/assets/StickyPagination-hMrnLXde.js.map +0 -1
  357. package/dashboard/dist/assets/TopicsPage-1VAc29Qw.js +0 -2
  358. package/dashboard/dist/assets/WorkflowExecutionPage-C15GSAoe.js +0 -2
  359. package/dashboard/dist/assets/WorkflowExecutionPage-C15GSAoe.js.map +0 -1
  360. package/dashboard/dist/assets/WorkflowsDashboard-BMQd_0tW.js +0 -2
  361. package/dashboard/dist/assets/capabilities-IaCcS7ge.js +0 -2
  362. package/dashboard/dist/assets/escalation-columns-BY2mg9DY.js +0 -2
  363. package/dashboard/dist/assets/escalation-columns-BY2mg9DY.js.map +0 -1
  364. package/dashboard/dist/assets/index-CLmZaNTK.js +0 -2
  365. package/dashboard/dist/assets/index-CfAi1OsI.js +0 -65
  366. package/dashboard/dist/assets/index-CfAi1OsI.js.map +0 -1
  367. package/dashboard/dist/assets/index-CvNBdkB1.js +0 -5
  368. package/dashboard/dist/assets/index-Dldi3Q3M.js +0 -2
  369. package/dashboard/dist/assets/index-mKcQGi2B.js +0 -3
  370. package/dashboard/dist/assets/index-mKcQGi2B.js.map +0 -1
  371. package/dashboard/dist/assets/index-zQiUPfxA.css +0 -1
  372. package/dashboard/dist/assets/useEventHooks-PDbuSJ12.js +0 -2
  373. package/dashboard/dist/assets/vendor-icons-BpZiWH2f.js.map +0 -1
@@ -0,0 +1,251 @@
1
+ # Escalation Analytics — Instrumenting a Process
2
+
3
+ Every escalation is one interval an entity spends in one state: it opens at
4
+ `created_at` and closes the instant the row leaves the live set. The
5
+ escalation queue is therefore a complete state time-series for every process
6
+ the platform runs, and the analytics surface reads it three ways — the
7
+ aggregate, the categorical slice, and the individual — from vocabulary the
8
+ workflow already writes plus two dials on the role.
9
+
10
+ This page is the whole authoring contract. The worked example is the seeded
11
+ printer scenario (`examples/seed-fleet-sim.ts`), which a fresh install
12
+ produces automatically; every call below runs verbatim against it.
13
+
14
+ ## The sixty-second contract
15
+
16
+ **1. The workflow parks every state as an escalation.** `role` is the queue
17
+ that attends the state. `subtype` names the state *within* the role, for
18
+ roles that hold several. `metadata` carries the entity key (stored as a JSON
19
+ string) plus any categorical facts worth slicing by.
20
+
21
+ ```typescript
22
+ // Inside a durable workflow: park the printer in the fleet queue, printing.
23
+ const result = await condition(signalId, {
24
+ activity: ltCreateEscalation,
25
+ config: {
26
+ type: 'fleet',
27
+ subtype: 'printing', // the state, within the printer-fleet role
28
+ role: 'printer-fleet', // the queue that attends it
29
+ metadata: {
30
+ serialNumber: 'PRN-001', // the entity
31
+ model: 'h2s', // categorical facts, sliceable
32
+ pdac: true,
33
+ foaming: false,
34
+ },
35
+ },
36
+ });
37
+ ```
38
+
39
+ **2. Each role declares two dials** (in the role admin page's Pace Board
40
+ section, beside the priority dials, or via `PATCH /api/roles/:role`):
41
+
42
+ | Dial | Meaning |
43
+ |------|---------|
44
+ | **Entity** (`entity_facet`) | The metadata key naming what moves through this role — `serialNumber` here. Roles sharing an entity key form that entity's **system**; the system is derived, never maintained. |
45
+ | **States from** (`entity_state_source`) | How the role names its contribution to the entity's state space. `'role'` (default): being in this queue is one state — a harvest or service bay. `'subtype'`: the role's subtypes are its states — a fleet role whose rows park as `idle` / `printing`. |
46
+
47
+ The printer scenario declares three roles:
48
+
49
+ ```
50
+ printer-fleet entity_facet: serialNumber states from: subtype (idle, printing)
51
+ printer-harvest entity_facet: serialNumber states from: role
52
+ printer-service entity_facet: serialNumber states from: role
53
+ ```
54
+
55
+ That is the entire contract. Everything below is reading it back.
56
+
57
+ ## The three questions
58
+
59
+ ### Q1 — the fleet: how did all printers spend their time?
60
+
61
+ One call: scope to the entity's system, group by the derived state, measure
62
+ dwell (open-seconds, clipped to the window).
63
+
64
+ ```bash
65
+ ltc esc aggregate-facets --entity serialNumber --group-state \
66
+ --window '{"from":"2026-08-01T00:00:00Z","to":"2026-08-01T12:00:00Z"}'
67
+ ```
68
+
69
+ ```typescript
70
+ const { data } = await client.escalations.aggregateByFacets({
71
+ query: { entity: 'serialNumber' },
72
+ groupBy: { state: true },
73
+ measure: { kind: 'dwell', window: { from, to } },
74
+ });
75
+ // → groups: [{ state: 'idle', dwellSeconds, ... }, { state: 'printing', ... },
76
+ // { state: 'printer-harvest', ... }, { state: 'printer-service', ... }]
77
+ ```
78
+
79
+ ```bash
80
+ curl -s -X POST http://localhost:3000/api/escalations/aggregate-by-facets \
81
+ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
82
+ -d '{"query":{"entity":"serialNumber"},"groupBy":{"state":true},
83
+ "measure":{"kind":"dwell","window":{"from":"2026-08-01T00:00:00Z","to":"2026-08-01T12:00:00Z"}}}'
84
+ ```
85
+
86
+ Each role contributes states per its dial: the fleet's subtypes (`idle`,
87
+ `printing`) plus the harvest and service roles themselves.
88
+
89
+ **Membership** is the other measure — rows (or, with `distinctBy`, distinct
90
+ entities) open at an instant. Omit `asOf` for now; a past `asOf` reconstructs
91
+ the live set at that moment.
92
+
93
+ ```typescript
94
+ // How many printers are in each state right now?
95
+ await client.escalations.aggregateByFacets({
96
+ query: { entity: 'serialNumber' },
97
+ groupBy: { state: true },
98
+ measure: { kind: 'membership' },
99
+ distinctBy: 'serialNumber',
100
+ });
101
+ // → one group per state, count = printers (not rows)
102
+ ```
103
+
104
+ ### Q2 — the slice: the same, compared by any facet
105
+
106
+ Q1 plus `groupBy.facets` — each value gets an independent state split, and a
107
+ NULL group key is a real group (rows missing the facet).
108
+
109
+ ```bash
110
+ ltc esc aggregate-facets --entity serialNumber --group-state --group-facets model \
111
+ --window '{"from":"2026-08-01T00:00:00Z","to":"2026-08-01T12:00:00Z"}'
112
+ # → p1s and h2s, each with its own idle/printing/harvest/service dwell
113
+ ```
114
+
115
+ Filtering works the same way: `--facets '{"pdac":true}'` restricts the whole
116
+ aggregate to the pdac printers.
117
+
118
+ ### Q3 — the individual: how did printer X spend its day?
119
+
120
+ The ordered interval sequence across the whole system, durations included.
121
+ Gaps between intervals are untracked time and are preserved — the settle
122
+ latency between queues is a first-class signal.
123
+
124
+ ```bash
125
+ ltc esc timeline serialNumber PRN-001 --entity serialNumber
126
+ ```
127
+
128
+ ```typescript
129
+ const { data } = await client.escalations.timelineByFacet({
130
+ facet: { key: 'serialNumber', value: 'PRN-001' },
131
+ query: { entity: 'serialNumber' },
132
+ });
133
+ // → intervals: [{ role, subtype, startedAt, endedAt, durationSeconds }, ...]
134
+ // open intervals report endedAt: null; gaps appear as time between spans
135
+ ```
136
+
137
+ ```bash
138
+ curl -s -X POST http://localhost:3000/api/escalations/timeline-by-facet \
139
+ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
140
+ -d '{"facet":{"key":"serialNumber","value":"PRN-001"},"query":{"entity":"serialNumber"}}'
141
+ ```
142
+
143
+ ## Where the answers appear in the dashboard
144
+
145
+ - **Q1** — `/operations`: the view selector offers every entity system beside
146
+ the station view; the system band shows state dwell over the selected
147
+ period with membership-now counts and the distinct-entity headline.
148
+ - **Q2** — the entity view's **Slice by** control: pick any metadata key the
149
+ rows carry and each value renders its own band, side by side.
150
+ - **Q3** — the entity table's timeline action, or the history affordance on
151
+ any metadata value in any escalation list: the full cross-queue journey in
152
+ the side panel, untracked gaps included.
153
+
154
+ Every surface refreshes from escalation events — resolve something and the
155
+ band moves within seconds.
156
+
157
+ ## The interval, precisely
158
+
159
+ `[created_at, ended_at)`. `ended_at` derives from the terminal transition:
160
+ `resolved_at` for resolved rows, `updated_at` for cancelled or expired rows
161
+ (both stamped inside their status-guarded transitions), NULL while the row is
162
+ live. The `lt_escalations` view exposes it as a computed column, and a
163
+ partial index on the terminal end instant keeps history scans bounded — one
164
+ index write per row, at its terminal transition.
165
+
166
+ `liveStatuses` (default `['pending']`) declares which statuses count as
167
+ live; everything else is terminal.
168
+
169
+ ## Locating one entity among thousands
170
+
171
+ Two filter fields make the individual findable at fleet scale, and both ride
172
+ the scans the aggregate already bounds — no additional indexes:
173
+
174
+ - **`prefix`** — case-insensitive prefix match on facet values
175
+ (`metadata->>key ILIKE 'value%'`, wildcards escaped). Type or scan the
176
+ leading characters of a serial and the entity table narrows:
177
+
178
+ ```bash
179
+ ltc esc aggregate-facets --entity serialNumber --group-facets serialNumber \
180
+ --prefix '{"serialNumber":"PRN-00"}' --window '{"from":"...","to":"..."}'
181
+ ```
182
+
183
+ - **`anyOf`** — the row carries at least one of the given facet sets
184
+ (`metadata @> ANY(...)`, GIN-served; max 200 entries). Targets an explicit
185
+ entity set — the current table page — in one query:
186
+
187
+ ```json
188
+ { "anyOf": [{ "serialNumber": "PRN-001" }, { "serialNumber": "PRN-002" }] }
189
+ ```
190
+
191
+ Result groups page with `orderBy` + `limit`/`offset`; `overflow: true` means
192
+ another page exists. In the dashboard, every entity row is deep-linkable:
193
+ `/operations?lens=serialNumber&entity=PRN-003` opens the fleet view focused
194
+ on that printer with its timeline panel raised, and `&find=PRN-00` restores
195
+ a narrowed table.
196
+
197
+ ## Paging a long timeline
198
+
199
+ Timelines order by start instant. The default `asc` reads the journey
200
+ top-down; a long history pages recent-first with `order: 'desc'` and the
201
+ `before` cursor (a strict upper bound on `startedAt`):
202
+
203
+ ```bash
204
+ ltc esc timeline serialNumber PRN-001 --entity serialNumber \
205
+ --order desc --limit 100
206
+ # then, for the next-older page:
207
+ ltc esc timeline serialNumber PRN-001 --entity serialNumber \
208
+ --order desc --limit 100 --before 2026-08-01T09:15:00Z
209
+ ```
210
+
211
+ The dashboard's timeline panel loads the most recent 100 intervals and
212
+ offers "Load earlier" until the history is exhausted.
213
+
214
+ ## Fail-loud validation
215
+
216
+ The filter takes the WHAT of a facet query only — `role`/`roles` (or
217
+ `entity`), `facets`, `block`, `range`, `exists`. Everything that would
218
+ silently change what an aggregate means is rejected with a 400:
219
+
220
+ - `status`, `available`, `jeopardy` on the filter — liveness derives from
221
+ the interval and the measure
222
+ - `orderBy`/`limit`/`offset` on the filter — paging applies to result groups
223
+ - `entity` together with `role`/`roles` — one scoping mechanism at a time
224
+ - an entity key no role declares — the response names the configuration gap
225
+ - `groupBy.state` together with `states[]` — one labeling mechanism
226
+ - a window wider than `LT_ANALYTICS_MAX_WINDOW_DAYS`, a future `asOf`,
227
+ malformed facet keys, unknown statuses
228
+
229
+ Result groups are capped (`LT_ANALYTICS_MAX_GROUPS`); when more exist the
230
+ response carries `overflow: true`.
231
+
232
+ ## Access
233
+
234
+ Aggregates and timelines require `read_all` on every role in scope —
235
+ entity-derived roles included. A query with no role scope spans every queue
236
+ and requires a global principal. With the public Pace Board flag on,
237
+ counts-only aggregates (no facet keys among the group keys) are readable by
238
+ any login — the same data class the station metrics expose; facet-keyed
239
+ groupings and timelines always take the full gate. See [iam.md](iam.md).
240
+
241
+ ## Reference
242
+
243
+ - HTTP: [api/http/escalations.md](api/http/escalations.md)
244
+ - SDK: `client.escalations.aggregateByFacets` / `timelineByFacet`
245
+ - MCP: `aggregate_by_facets` / `timeline_by_facet` on the admin server
246
+ - CLI: `ltc esc aggregate-facets`, `ltc esc timeline` — [cli.md](cli.md)
247
+ - Config: `LT_ANALYTICS_MAX_GROUPS`, `LT_ANALYTICS_MAX_WINDOW_DAYS`,
248
+ `LT_ANALYTICS_CACHE_TTL_MS`, `LT_ANALYTICS_PAST_CACHE_TTL_MS`,
249
+ `LT_ANALYTICS_CACHE_MAX_ENTRIES`
250
+
251
+ To watch the seeded fleet move live: `npm run printers:cycle`.
@@ -57,7 +57,7 @@ const decision = await conditionLT<{ confirmed: boolean; address: string }>(sign
57
57
 
58
58
  ## Escalation Chains
59
59
 
60
- Users can escalate to other roles via the "Escalate" tab on the detail page. Configure chain targets in Admin > Roles. Each role lists which other roles it can escalate to. Common shape:
60
+ Chains are role-to-role routing edges, configured via the roles API (`POST /api/roles/escalation-chains`). Each role lists which other roles it can route work to, and the edges scope the programmatic re-routing surfaces — the `escalate_and_wait` MCP tool and the escalate APIs (HTTP, SDK, MCP, CLI). Common shape:
61
61
 
62
62
  ```
63
63
  Analyst → Senior Analyst → Manager → VP
@@ -227,7 +227,8 @@ A top-level form_schema token (a peer of `x-lt-help`) that keeps the resolve but
227
227
  "assigned": "me"
228
228
  },
229
229
  "mustBeEmpty": true,
230
- "message": "{{count}} item(s) still pending — pick them before closing the order."
230
+ "message": "{{count}} item(s) still pending — pick them before closing the order.",
231
+ "autoResolveWhenEmpty": true
231
232
  },
232
233
  "type": "object",
233
234
  "properties": { ... }
@@ -239,12 +240,15 @@ A top-level form_schema token (a peer of `x-lt-help`) that keeps the resolve but
239
240
  | `query` | `object` | Same shape as `x-lt-query` — including the `assigned` ownership scope; facet string values interpolate against the host escalation's context |
240
241
  | `mustBeEmpty` | `boolean` | The gate condition (default `true`) |
241
242
  | `message` | `string` | Shown beside the disabled submit; `{{count}}` carries the live row count, `{{domain.path}}` tokens also interpolate |
243
+ | `autoResolveWhenEmpty` | `boolean` | Auto-submit the claimed parent the moment the query is confirmed empty |
242
244
 
243
- The guard and an escalation-list embed declaring the same query resolve through one shared mapping — the count and the visible rows always agree, whatever the scope.
245
+ The guard and an escalation-list embed declaring the same query resolve through one shared mapping — the count and the visible rows always agree, whatever the scope. Pair it with an `escalation-list` embed on the same query so the operator sees exactly which rows are holding the gate.
244
246
 
245
- Pair it with an `escalation-list` embed on the same query so the operator sees exactly which rows are holding the gate.
247
+ **Only a confirmed empty read clears the gate** — a successful query returning zero rows. While it is loading, or on an error or a 403, the submit stays disabled: the gate opens only when it can prove there is nothing left.
246
248
 
247
- The guard is a UI layer: it makes the form honest, it does not enforce. A resolve arriving through the raw API bypasses it by design the consuming workflow's own verification (reject and re-park with the remainder) is the durable backstop. Triage is never gated: when the guarded work itself is the problem, "Send to Triage" stays available.
249
+ **Enforced on both sides.** The dashboard disables the submit; for `enforce_schema` roles the API server runs the same query and rejects a resolve with a canonical `422` while it returns rows, so the rule holds through the raw API too. The server counts the true children regardless of the resolver's read scope, so a role that cannot see them can never falsely clear the gate. A surface with no resolving user (an MCP resolve of an `assigned:"me"` guard) and roles that do not `enforce_schema` fall back to the consuming workflow's own verification (reject and re-park with the remainder) as the durable backstop. Triage is never gated: when the guarded work itself is the problem, "Send to Triage" stays available.
250
+
251
+ **`autoResolveWhenEmpty`** closes the loop. With it set, the claimed parent submits itself the moment the query is confirmed empty — re-checked on page-load and after each inline child-resolve — so a person clears the children (resolving them in place via `x-lt-actions`) and the parent closes with no extra click. A parent launched from a list with [`x-lt-submit-on-claim`](./x-lt-footer.md) that carries a guard claims, shows its children, and auto-closes as they drain; if the parent has a [transition](./x-lt-transition.md), it then hands off to the follow-on. The parent's own form must still validate — an incomplete parent is left for the person rather than auto-closed.
248
252
 
249
253
  ---
250
254
 
@@ -0,0 +1,67 @@
1
+ # x-lt-footer — the escalation footer, configured
2
+
3
+ A role's versioned `form_schema` can, at its root, shape the standard escalation
4
+ footer: rename its action controls and fold claim-and-submit into one gesture.
5
+ Both tokens are opt-in top-level form tokens, siblings of
6
+ [`x-lt-transition`](./x-lt-transition.md) and `x-lt-submit-guard`
7
+ ([x-lt-embed.md](./x-lt-embed.md)).
8
+
9
+ ## Tokens (schema root)
10
+
11
+ | Token | Type | Meaning |
12
+ |-------|------|---------|
13
+ | `x-lt-submit-on-claim` | boolean | Claiming also resolves, submitting whatever the form holds. Defaults off. |
14
+ | `x-lt-labels` | object | Per-target overrides for the footer's action labels. |
15
+
16
+ ## `x-lt-labels` — configurable copy
17
+
18
+ An object whose keys are standard footer targets and whose values are the labels
19
+ to render in their place:
20
+
21
+ | Target | Default | Control |
22
+ |--------|---------|---------|
23
+ | `claim` | Claim | The claim button (unclaimed). |
24
+ | `cancel` | Cancel / Cancel escalation | The cancel control, both states. |
25
+ | `submit` | Submit / Acknowledge | The resolve button (claimed). |
26
+ | `release` | Release | The release tab. |
27
+
28
+ ```jsonc
29
+ {
30
+ "type": "object",
31
+ "x-lt-labels": { "claim": "Claim and Submit", "submit": "Approve" },
32
+ "properties": { /* … */ }
33
+ }
34
+ ```
35
+
36
+ Only the known targets are read; unknown keys and non-string values are
37
+ ignored. Any target the schema omits keeps its default, so a schema overrides
38
+ just the controls it cares about. The pending, triage, and confirmation states
39
+ ("Claiming…", "Send to Triage", "Yes, Release") keep their own copy.
40
+
41
+ ## `x-lt-submit-on-claim` — the one-gesture claim
42
+
43
+ When true, clicking Claim also resolves the escalation in the same gesture,
44
+ submitting the form's current values — its seeded defaults (metadata,
45
+ `formDefaults`, or schema `default`), or a restored draft. The person advances
46
+ the work item with a single click; there is no second Submit step.
47
+
48
+ ```jsonc
49
+ {
50
+ "type": "object",
51
+ "x-lt-submit-on-claim": true,
52
+ "x-lt-labels": { "claim": "Claim and Submit" },
53
+ "properties": {
54
+ "approved": { "type": "boolean", "default": true }
55
+ }
56
+ }
57
+ ```
58
+
59
+ Claim and resolve are distinct operations — the server accepts a resolve only
60
+ from the claimant — so they run in sequence: claim, then resolve. If the seeded
61
+ defaults fail validation the claim still stands and the page drops into the
62
+ normal claimed state with the field errors in the side panel, so the person
63
+ completes the form by hand. Pair it with `x-lt-labels.claim` so the button reads
64
+ honestly ("Claim and Submit").
65
+
66
+ Use it when the standard outcome needs only a confirmation — the defaults are
67
+ the answer, and claiming is the decision.
@@ -31,9 +31,10 @@ Every layout carries a persistent action button — the active card's CTA, a tra
31
31
 
32
32
  ```jsonc
33
33
  "x-lt-row-action": {
34
- "action": "claim", // "claim" | "view" — default "claim"
35
- "label": "Service", // button text; defaults "Claim" / "View"
36
- "durationMinutes": 60 // claim hold time; default 30
34
+ "action": "claim", // "claim" | "view" — default "claim"
35
+ "label": "Service", // button text; defaults "Claim" / "View"
36
+ "durationMinutes": 60, // claim hold time; default 30
37
+ "submitOnClaim": true // claim AND submit, then transition on (see below)
37
38
  }
38
39
  ```
39
40
 
@@ -41,6 +42,12 @@ Every layout carries a persistent action button — the active card's CTA, a tra
41
42
 
42
43
  On My Escalations (`/escalations/queue`) every row is already held by the viewer, so a `claim` template renders as a View action there: the button opens the detail page with the default "View" label. Authored `view` actions keep their label.
43
44
 
45
+ ### Start from the list — `submitOnClaim`
46
+
47
+ `submitOnClaim: true` turns the row action into a one-gesture start: the row is claimed and its form's seeded defaults are submitted, then — when the resolve spawns a follow-on born assigned to the person — the wait screen bridges to it. A "Start Harvesting" button on the list claims, submits, and lands the person on the next step, no detail-page stop in between.
48
+
49
+ The behavior itself lives on the form: the claim-and-submit and the hand-off are the form schema's [`x-lt-submit-on-claim`](./x-lt-footer.md) and [`x-lt-transition`](./x-lt-transition.md). The list action only launches that flow with the authored claim duration — so one role owns the outcome and the list template just points to it. If the seeded defaults fail validation the claim still lands and the person finishes on the detail page. Applies to claimable rows; on My Escalations the row already belongs to the viewer, so it opens as a View.
50
+
44
51
  ---
45
52
 
46
53
  ## Layouts
@@ -20,6 +20,8 @@ This works because the form is data: a JSON Schema stored on the role, versioned
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
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
+ | Rename the resolve footer, or claim-and-submit in one gesture | Footer tokens | [x-lt-footer.md](hitl/x-lt-footer.md) |
24
+ | Block the resolve until embedded work drains, then auto-close when it does | `x-lt-submit-guard` | [x-lt-embed.md](hitl/x-lt-embed.md) |
23
25
  | A fully custom UI nothing above can express | Iframe viewport | [iframe.md](hitl/iframe.md) |
24
26
 
25
27
  ---
@@ -59,12 +61,12 @@ When you author a HITL-backed workflow, the platform handles:
59
61
  - **Accessible forms** — generated controls carry label association, error announcements, and keyboard-correct locking (see [form.md](hitl/form.md#accessibility))
60
62
  - **Side panel** — help, AI analysis, metadata, context, and raw-record views beside the form
61
63
  - **Section state persistence** — collapsed sections remembered across navigation
62
- - **Escalation chains** — users can re-route work to other roles
64
+ - **Escalation chains** — role-to-role routing edges that scope programmatic re-routing (API, MCP, CLI)
63
65
  - **AI triage** — optional auto-resolution for common patterns
64
66
  - **Credential security** — password fields use ephemeral tokens, never stored in plain text
65
67
  - **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))
66
68
  - **Telemetry** — trace IDs link escalations to OpenTelemetry traces
67
- - **Bulk operations** — bulk claim, assign, escalate, triage, and cancel for queue management
69
+ - **Bulk operations** — bulk claim, assign, triage, and cancel for queue management
68
70
  - **Cancellation** — cancel pending escalations from the API or dashboard
69
71
 
70
72
  You write the workflow and the schema. Everything else is provided.
@@ -84,6 +86,8 @@ Ordered as a learning path — each file adds one capability to the same form:
84
86
  | Layout, ordering, sections, binding, help panel | [x-lt-layout.md](hitl/x-lt-layout.md) |
85
87
  | Custom widgets (checklist, file upload, code editor, signature, markdown) | [x-lt-widget.md](hitl/x-lt-widget.md) |
86
88
  | List schema (`active-history`, `active`, `facet-table`, `facet-board` cards) | [x-lt-list-schema.md](hitl/x-lt-list-schema.md) |
89
+ | Embedded widgets (`link`, `escalation`, `escalation-list`) and the submit guard | [x-lt-embed.md](hitl/x-lt-embed.md) |
90
+ | Footer labels, claim-and-submit, and the list-driven "start" action | [x-lt-footer.md](hitl/x-lt-footer.md) |
87
91
  | Iframe viewport protocol | [iframe.md](hitl/iframe.md) |
88
92
  | Claim lifecycle, resolving from system code, outcome recording, cancellation | [resolution.md](hitl/resolution.md) |
89
93
  | Role routing, RBAC, scope, chains | [roles.md](hitl/roles.md) |
@@ -118,7 +122,12 @@ Ordered as a learning path — each file adds one capability to the same form:
118
122
  | `x-lt-help` | schema | Markdown guidance for the side panel's Help view |
119
123
  | `x-lt-context` | schema | Plain-text fallback for the Help view when `x-lt-help` is absent |
120
124
  | `x-lt-viewport` | schema | Replace the generated form with a custom iframe UI |
125
+ | `x-lt-submit-guard` | schema | Block the resolve while an embedded escalation query returns rows; `autoResolveWhenEmpty` auto-closes the claimed parent once it drains. Enforced isomorphically for `enforce_schema` roles |
126
+ | `x-lt-submit-on-claim` | schema | Claiming also resolves the form's seeded defaults in one gesture |
127
+ | `x-lt-labels` | schema | Per-target labels for the footer controls (`claim`, `cancel`, `submit`, `release`) |
128
+ | `x-lt-transition` | schema | Pause on a wait screen after resolve and hand off to the born-assigned follow-on |
121
129
  | `x-lt-columns` | schema (list) | Column definitions for `facet-table` layout |
130
+ | `x-lt-row-action` | schema (list) | Per-row list action: `claim` / `view`, or `submitOnClaim` to claim, submit, and transition straight from the list |
122
131
  | `x-lt-active` | schema (list) | Active-item card definition |
123
132
  | `x-lt-history` | schema (list) | History column definition |
124
133
  | `format` | field | Input specialization: `password`, `date`, `date-time`, `email`, `uri`, `textarea` |
package/docs/iam.md CHANGED
@@ -92,6 +92,12 @@ HTTP surface: `GET/POST /api/personas`, `GET/PATCH/DELETE /api/personas/:key`, `
92
92
 
93
93
  Scope folds into the SQL, so it is atomic with no TOCTOU window. Read scope becomes part of the escalation search query — a member sees a row when `role ∈ allRoles OR (role ∈ selfRoles AND assigned_to = me)`. Write scope folds into the atomic resolve-by-metadata query and gates the by-id claim/resolve/cancel paths. `assigned_to` is indexed, so `self` scope scales to large queues.
94
94
 
95
+ ### Analytics gate
96
+
97
+ The escalation analytics surfaces (`aggregate-by-facets`, `timeline-by-facet`) are reads that aggregate other operators' items, and they may span roles — so the gate is `read_all` on **every** role in the query's scope. When the filter uses `entity`, the scope is the derived system: every role declaring that entity facet. A query with no role scope at all is inherently cross-role and requires a global principal (superadmin/admin).
98
+
99
+ While the `features.publicPaceBoard` flag stands (default on), counts-only aggregates — groupings with no `groupBy.facets` keys — are readable by any login: they emit counts and seconds, the same data class station metrics already expose to everyone. Facet-keyed groupings emit facet values (entity ids) as group keys, and a timeline is one entity's movement history — item-level disclosure — so both always take the full `read_all`-per-role gate.
100
+
95
101
  ## Workflow Types and IAM
96
102
 
97
103
  Long Tail has three workflow types (see the [Workflows Guide](workflows.md#three-workflow-types) for full details). IAM applies to all three:
@@ -239,6 +245,12 @@ If a token is expired or exhausted, the opaque string passes through unchanged.
239
245
 
240
246
  When an activity calls `getCredential('anthropic')` and no credential exists in the cascade (principal → initiator → env var), `MissingCredentialError` is thrown. The interceptor catches this and creates a credential-focused escalation with `category: 'missing_credential'`. The escalation form can include a password field so the human provides the credential ephemerally, without it being stored permanently.
241
247
 
248
+ ### Acting identity (badge grants)
249
+
250
+ The same keystore carries **acting-identity grants** for shared station devices. A device signs in once as a station account — `read_scope: 'all'`, `write_scope: 'none'` on the queues it fronts — so reads are free and every mutation is structurally blocked for the account itself. A badge scan under an identity-kind scan scheme resolves the badge token against a server-side binding (`lt_users.metadata`, e.g. `badge_id`) and mints `eph:v1:acting_identity:<uuid>` under the scheme's TTL/max-uses policy.
251
+
252
+ The grant rides scan requests as `actingToken` and is exchanged server-side before anything reads or writes: the verbs run **as the badged person under their own live RBAC**. The grant confers attribution, never privilege — a badged user without write scope on the queue is still denied. Mutations attribute to the person (`assigned_to`, `resolved_by`) with the device recorded beside them (`scanStation` provenance). A dead grant is a loud `not_primed` outcome, never a silent execution as the device. See [scan-codes.md](scan-codes.md#identity-schemes-and-acting-identity).
253
+
242
254
  ## Dashboard
243
255
 
244
256
  The dashboard surfaces IAM across four pages:
@@ -90,6 +90,23 @@ A 340 px right rail that slides open when a row or chart circle is clicked. Thre
90
90
 
91
91
  Close the panel with × or by clicking another row.
92
92
 
93
+ ## Entity lens
94
+
95
+ The board carries one view selector. When any station declares an entity facet, a lens strip appears above the chart: **Stations** (the default station-first board) plus one lens per entity facet the visible roles declare, labeled `by <key>` (e.g. `by serialNumber`). The active lens is deep-linked (`?lens=<key>`). Selecting a lens flips the page from station-first to entity-first: roles sharing the facet form the entity's **system**, and each role contributes states per its `entity_state_source`. Three tiers, aggregate → individual:
96
+
97
+ - **The system band** — how the fleet's time splits across states over the selected period: one stacked dwell band with a per-state legend (duration, percentage, and a `· N now` membership count where entities currently sit), headlined by the distinct-entity count in queue now (e.g. `6 serialNumber in queue now`).
98
+ - **Slice by** — the same band per value of any metadata key, chosen from a select of the known facet keys (e.g. `model` splits the band into `p1s` vs `h2s`). Small-multiple bands, ranked by total dwell, top 8 values.
99
+ - **The entity table** — one row per entity, ranked by tracked time: the entity value, its current state, its own dwell band, its total tracked time, and a timeline action that opens the entity's cross-queue interval timeline in the right panel.
100
+
101
+ The lens is driven by two dials on the role's Pace Board section ([Role Detail](dashboard.md#role-detail)):
102
+
103
+ | Dial | Description |
104
+ |------|-------------|
105
+ | `entity_facet` | The `lt_escalations.metadata` key naming the entity that moves through the role (e.g. `serialNumber`, `orderId`). Roles sharing a key form that entity's system. Blank = the role has no entity notion. |
106
+ | `entity_state_source` | How the role names the entity's state: **Station** (`'role'` — being in this role is one state, e.g. a servicing queue) or **Subtypes** (`'subtype'` — the one role holds several states named by each escalation's subtype, e.g. a fleet role parking `idle` / `printing`). Default `'role'`. |
107
+
108
+ The band tiers are counts-only aggregates, readable by any login while the public board flag stands; the slice and per-entity tiers group by facet values and require full (`read_all`) access to the system's queues. Data comes from `POST /api/escalations/aggregate-by-facets` and `POST /api/escalations/timeline-by-facet` — see [escalation-analytics.md](escalation-analytics.md) for the query contract.
109
+
93
110
  ## Data source
94
111
 
95
112
  All station metrics come from `GET /api/escalations/station-metrics?period=<period>`. The endpoint runs two queries against `public.hmsh_escalations` joined to `lt_roles` (for the priority dials and `target_per_hour`): a live-counts pass over the pending backlog, and a window-bounded percentile pass over resolved rows (`PERCENTILE_CONT` in Postgres, served by the `idx_hmsh_esc_resolved_cover` index). Updates reach the page as push events — every escalation write invalidates the metrics through the Socket.IO event system.
@@ -22,6 +22,9 @@ canonical action surface. A scan is an **ECA rule** over that surface:
22
22
  - [Schemes](#schemes)
23
23
  - [Rules and steps](#rules-and-steps)
24
24
  - [Confirmation](#confirmation)
25
+ - [Info-choice screens](#info-choice-screens)
26
+ - [Identity schemes and acting identity](#identity-schemes-and-acting-identity)
27
+ - [Station deployments](#station-deployments)
25
28
  - [The fallback screen](#the-fallback-screen)
26
29
  - [Executing a scan](#executing-a-scan)
27
30
  - [Capture on the dashboard](#capture-on-the-dashboard)
@@ -122,6 +125,115 @@ rule's prompt ("Cancel this printer's current state and send it home to
122
125
  servicing?"). Confirming fires the standard per-id endpoint — the same
123
126
  guarded call every other surface uses.
124
127
 
128
+ ## Info-choice screens
129
+
130
+ Some objects carry one code for their whole life — an item tag, an order
131
+ label. One code, many possible intents, and the right one depends on where
132
+ the object is in its journey. The `present` verb closes that gap: the step
133
+ locates the row, states its reality, and returns a configured, labeled
134
+ choice set for the human to pick from. Upper half — what is true; lower
135
+ half — what you may do about it.
136
+
137
+ ```jsonc
138
+ {
139
+ "query": { "roles": ["printer-fleet", "printer-harvest", "printer-service"] },
140
+ "verb": "present",
141
+ "choices": [
142
+ { "label": "Claim & Start", "verb": "claim", "requireActingIdentity": true, "code": "CLAIM" },
143
+ { "label": "Complete", "verb": "resolve", "requireActingIdentity": true,
144
+ "confirm": { "prompt": "Mark this item complete?" },
145
+ "params": { "resolverPayload": { "outcome": "complete" } } },
146
+ { "label": "View Details", "verb": "show-detail" }
147
+ ]
148
+ }
149
+ ```
150
+
151
+ The scan answers `choices` with the located escalation and the choice list;
152
+ each choice carries `withheld: true` when its identity requirement is
153
+ unsatisfied. Picking one calls `POST /api/scan-codes/execute-choice` with a
154
+ pointer (scheme, category, step index, choice index, escalation id) — and a
155
+ pointer is never authority: the server re-reads live config, re-locates the
156
+ row under the step's query, re-applies the identity gate, and runs the verb
157
+ through the same atomic executors a direct scan uses. A row that moved on
158
+ between render and tap answers `conflict`, exactly as a lost double-scan.
159
+
160
+ A choice's `code` is a short printable token (letters, digits, underscore,
161
+ dash) enabling double-scan selection: scan the object, then scan an action
162
+ card. The station screen matches the second scan against the presented
163
+ choices before treating it as a new code.
164
+
165
+ A claim choice lands on the work: after `claim` or `claim-show-detail`
166
+ executes, the station navigates to the escalation's detail page — the same
167
+ form every operator uses to resolve, reject, or conclude the item.
168
+
169
+ **One scan, one action.** A step with exactly one confirm-less choice can
170
+ set `autoSelectSingle: true`: the scan executes the choice directly instead
171
+ of presenting a one-button screen. The worker scans the item that just
172
+ arrived at their bench and it is theirs, claimed for the configured
173
+ duration, form on screen. An unsatisfied identity requirement never
174
+ auto-fires as the wrong actor — the scan stops over at the badge screen and
175
+ completes on its own once a badge primes.
176
+
177
+ ## Identity schemes and acting identity
178
+
179
+ A scheme with `kind: "identity"` is the badge layer. Its `target_facet`
180
+ names the `lt_users.metadata` key the scanned badge token matches (the demo
181
+ binds `badge_id`) — resolution is only ever that metadata equality, so a
182
+ printed username can never impersonate. Identity rules carry no steps;
183
+ their `fallback` is the unknown-badge screen.
184
+
185
+ A matching badge scan answers `identity_primed` with the person's display
186
+ name, an expiry, and an **acting grant** — an ephemeral token
187
+ (`eph:v1:acting_identity:…`) minted through the internal keystore under the
188
+ scheme's policy:
189
+
190
+ | Scheme field | Meaning |
191
+ |---|---|
192
+ | `grant_ttl_seconds` | How long the grant lives (1–86400). |
193
+ | `grant_max_uses` | `0` = TTL-bound; `n` = the grant covers n scan requests (a strict one-scan policy is `1`). |
194
+
195
+ The grant rides subsequent scans as `actingToken`. Verbs then run **as the
196
+ badged person under their own live RBAC** — the grant confers attribution,
197
+ never privilege. A dead grant (expired, exhausted, revoked) is a loud
198
+ `not_primed`, never a silent execution as the device. Scanning the next
199
+ badge replaces the session's grant; passing `previousActingToken` revokes
200
+ the outgoing one immediately.
201
+
202
+ Steps and choices opt in with `requireActingIdentity: true`: the effective
203
+ actor must be a real acting identity — a badge grant, or an authenticated
204
+ user whose own write scope covers the step. When unsatisfied, the outcome
205
+ is `not_primed` with the rule's `notPrimed` screen (a sibling of
206
+ `fallback`): the "scan your badge" message, never a silent or misattributed
207
+ action.
208
+
209
+ ## Station deployments
210
+
211
+ A shared floor device (tablet + tethered scanner) signs in once as a
212
+ station account — an ordinary user with `read_scope: 'all'` and
213
+ `write_scope: 'none'` on the queues it fronts. Reads are free: the station
214
+ holds a real seat with a queue view and an audit identity. Every mutation
215
+ takes the badge layer, and `write_scope: 'none'` is the structural
216
+ backstop — even a misconfigured rule cannot mutate as the station, because
217
+ the RBAC write gate denies the account itself.
218
+
219
+ Mutations attribute to the badged person (`assigned_to`, `resolved_by`)
220
+ with the device recorded beside them (`scanStation` in the scan
221
+ provenance) — every action individually owned and geographically placed.
222
+ The dashboard's `/scan/station` route is the full-screen experience: idle
223
+ ("scan your badge / scan an item"), the primed chrome with the grant
224
+ countdown, the info-choice screen, and the **badge stop-over** — the one
225
+ identity moment the flow ever surfaces. When an action needs a person and
226
+ none is primed (a withheld choice tapped, an auto-select scan, a grant that
227
+ lapsed mid-flow), the screen becomes a single clear prompt: scan your badge
228
+ to continue. The badge scan primes the session and the pending action
229
+ completes on its own. With a live grant the stop-over never appears — the
230
+ identity layer is invisible when all is well.
231
+
232
+ Badge tokens are printed credentials: seed them as long random strings,
233
+ bind them server-side (`lt_users.metadata`), and treat badge possession
234
+ with the same physical policy as any badge system. Unknown badges answer
235
+ `identity_unknown` and are auditable.
236
+
125
237
  ## The fallback screen
126
238
 
127
239
  When no step matches, the response carries the rule's `fallback`: markdown
package/docs/sdk.md CHANGED
@@ -63,7 +63,7 @@ The client mirrors the REST route structure:
63
63
  | Namespace | REST equivalent | Key operations |
64
64
  |-----------|----------------|----------------|
65
65
  | `lt.tasks` | `/api/tasks` | `list`, `get`, `listProcesses`, `getProcess`, `getProcessStats` |
66
- | `lt.escalations` | `/api/escalations` | `list`, `listAvailable`, `get`, `claim`, `release`, `resolve`, `escalate`, `bulkClaim`, `bulkAssign`, `findByMetadata`, `claimByMetadata`, `resolveByMetadata` |
66
+ | `lt.escalations` | `/api/escalations` | `list`, `listAvailable`, `get`, `claim`, `release`, `resolve`, `escalate`, `bulkClaim`, `bulkAssign`, `findByMetadata`, `claimByMetadata`, `resolveByMetadata`, `aggregateByFacets`, `timelineByFacet` — see the [Escalations SDK reference](api/sdk/escalations.md) |
67
67
  | `lt.workflows` | `/api/workflows` | `invoke`, `getStatus`, `getResult`, `terminate`, `listWorkers`, `listDiscovered`, `listConfigs`, `getConfig`, `upsertConfig` |
68
68
  | `lt.yamlWorkflows` | `/api/yaml-workflows` | `list`, `get`, `create`, `deploy`, `activate`, `invoke`, `archive` |
69
69
  | `lt.users` | `/api/users` | `list`, `get`, `create`, `update`, `delete`, `getRoles`, `addRole`, `removeRole` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.10.13",
3
+ "version": "0.11.0",
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",
@@ -45,6 +45,7 @@
45
45
  "demo": "bash scripts/demo.sh",
46
46
  "ortho:populate": "ts-node scripts/ortho-populate.ts",
47
47
  "twin:farm": "ts-node scripts/twin-farm.ts",
48
+ "printers:cycle": "ts-node scripts/printer-cycle.ts",
48
49
  "dashboard:install": "cd dashboard && npm install",
49
50
  "dashboard:dev": "cd dashboard && npm run dev",
50
51
  "dashboard:build": "cd dashboard && npm run build",