@hotmeshio/long-tail 0.22.1 → 0.22.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 (331) hide show
  1. package/build/api/escalations/accumulate.js +38 -19
  2. package/build/api/escalations/lookups.d.ts +9 -2
  3. package/build/api/escalations/lookups.js +15 -3
  4. package/build/api/escalations/metadata.d.ts +2 -0
  5. package/build/api/escalations/metadata.js +9 -0
  6. package/build/api/escalations/resolve-batch.js +18 -11
  7. package/build/api/roles.js +4 -0
  8. package/build/api/scan-codes/badge-policy.d.ts +10 -0
  9. package/build/api/scan-codes/badge-policy.js +72 -0
  10. package/build/api/scan-codes/choice.js +16 -10
  11. package/build/api/scan-codes/context.d.ts +45 -1
  12. package/build/api/scan-codes/context.js +51 -3
  13. package/build/api/scan-codes/execute.js +73 -35
  14. package/build/api/scan-codes/grant.d.ts +27 -0
  15. package/build/api/scan-codes/grant.js +67 -0
  16. package/build/api/scan-codes/hold.d.ts +10 -0
  17. package/build/api/scan-codes/hold.js +98 -0
  18. package/build/api/scan-codes/identity.d.ts +5 -2
  19. package/build/api/scan-codes/identity.js +26 -8
  20. package/build/api/scan-codes/locate.js +4 -0
  21. package/build/api/scan-codes/present.d.ts +1 -1
  22. package/build/api/scan-codes/present.js +43 -0
  23. package/build/api/scan-codes/subject.d.ts +15 -0
  24. package/build/api/scan-codes/subject.js +68 -0
  25. package/build/api/scan-codes/verb-accumulate.d.ts +24 -0
  26. package/build/api/scan-codes/verb-accumulate.js +151 -0
  27. package/build/api/scan-codes/verb-fill.d.ts +6 -0
  28. package/build/api/scan-codes/verb-fill.js +155 -0
  29. package/build/api/scan-codes/verb-into-subject.d.ts +12 -0
  30. package/build/api/scan-codes/verb-into-subject.js +157 -0
  31. package/build/api/scan-codes/verbs-subject.d.ts +33 -0
  32. package/build/api/scan-codes/verbs-subject.js +247 -0
  33. package/build/api/scan-codes/verbs.d.ts +11 -29
  34. package/build/api/scan-codes/verbs.js +51 -131
  35. package/build/index.d.ts +1 -0
  36. package/build/index.js +9 -2
  37. package/build/lib/db/schemas/037_scan_gtin_and_subject_grants.sql +27 -0
  38. package/build/routes/escalations/acting.d.ts +5 -0
  39. package/build/routes/escalations/acting.js +8 -1
  40. package/build/routes/scan-codes.js +3 -0
  41. package/build/sdk/index.d.ts +1 -0
  42. package/build/services/escalation/claim-asserted.d.ts +10 -0
  43. package/build/services/escalation/claim-asserted.js +64 -0
  44. package/build/services/escalation/facets.d.ts +3 -1
  45. package/build/services/escalation/facets.js +7 -3
  46. package/build/services/escalation/index.d.ts +1 -0
  47. package/build/services/escalation/index.js +3 -1
  48. package/build/services/iam/acting-identity.d.ts +29 -4
  49. package/build/services/iam/acting-identity.js +60 -17
  50. package/build/services/iam/ephemeral.d.ts +21 -0
  51. package/build/services/iam/ephemeral.js +32 -1
  52. package/build/services/iam/sql.d.ts +10 -0
  53. package/build/services/iam/sql.js +32 -3
  54. package/build/services/knowledge/lookup-cache.d.ts +16 -4
  55. package/build/services/knowledge/lookup-cache.js +53 -20
  56. package/build/services/knowledge/lookup-refs.d.ts +3 -2
  57. package/build/services/knowledge/lookup-refs.js +6 -4
  58. package/build/services/knowledge/sql.d.ts +2 -0
  59. package/build/services/knowledge/sql.js +9 -1
  60. package/build/services/mcp/server-tools.js +1 -1
  61. package/build/services/orchestrator/condition.d.ts +13 -10
  62. package/build/services/orchestrator/condition.js +8 -6
  63. package/build/services/role/badge-grant.d.ts +5 -0
  64. package/build/services/role/badge-grant.js +36 -0
  65. package/build/services/role/seed.js +5 -0
  66. package/build/services/role/types.d.ts +14 -0
  67. package/build/services/role/types.js +5 -0
  68. package/build/services/scan-code/index.d.ts +3 -2
  69. package/build/services/scan-code/index.js +8 -1
  70. package/build/services/scan-code/parse.d.ts +23 -20
  71. package/build/services/scan-code/parse.js +45 -90
  72. package/build/services/scan-code/read.js +1 -0
  73. package/build/services/scan-code/sql.d.ts +3 -3
  74. package/build/services/scan-code/sql.js +11 -8
  75. package/build/services/scan-code/validate-facts.d.ts +5 -0
  76. package/build/services/scan-code/validate-facts.js +24 -0
  77. package/build/services/scan-code/validate-query.d.ts +8 -0
  78. package/build/services/scan-code/validate-query.js +36 -0
  79. package/build/services/scan-code/validate-subject.d.ts +7 -0
  80. package/build/services/scan-code/validate-subject.js +185 -0
  81. package/build/services/scan-code/validate.d.ts +7 -0
  82. package/build/services/scan-code/validate.js +103 -18
  83. package/build/services/scan-code/write.d.ts +1 -0
  84. package/build/services/scan-code/write.js +9 -3
  85. package/build/shared/form-validation/field-validator.js +4 -0
  86. package/build/shared/form-validation/index.d.ts +1 -0
  87. package/build/shared/form-validation/index.js +1 -0
  88. package/build/shared/form-validation/x-lt-scan.d.ts +52 -0
  89. package/build/shared/form-validation/x-lt-scan.js +166 -0
  90. package/build/shared/scan-code/gtin.d.ts +11 -0
  91. package/build/shared/scan-code/gtin.js +38 -0
  92. package/build/shared/scan-code/index.d.ts +2 -0
  93. package/build/shared/scan-code/index.js +11 -0
  94. package/build/shared/scan-code/parse.d.ts +46 -0
  95. package/build/shared/scan-code/parse.js +133 -0
  96. package/build/system/mcp-servers/admin/scan-codes.js +10 -4
  97. package/build/system/mcp-servers/admin/schemas.d.ts +939 -28
  98. package/build/system/mcp-servers/admin/schemas.js +72 -6
  99. package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -6
  100. package/build/system/seed/tool-manifests-admin.d.ts +202 -1
  101. package/build/system/seed/tool-manifests-admin.js +2 -2
  102. package/build/tsconfig.tsbuildinfo +1 -1
  103. package/build/types/escalation.d.ts +16 -8
  104. package/build/types/escalation.js +18 -7
  105. package/build/types/index.d.ts +4 -4
  106. package/build/types/index.js +10 -1
  107. package/build/types/scan-code/constants.d.ts +182 -0
  108. package/build/types/{scan-code.js → scan-code/constants.js} +72 -11
  109. package/build/types/scan-code/index.d.ts +3 -0
  110. package/build/types/scan-code/index.js +17 -0
  111. package/build/types/scan-code/response.d.ts +145 -0
  112. package/build/types/scan-code/response.js +2 -0
  113. package/build/types/scan-code/rule.d.ts +229 -0
  114. package/build/types/scan-code/rule.js +2 -0
  115. package/build/types/startup.d.ts +3 -1
  116. package/dashboard/dist/assets/{AdminDashboard-BbWve-Ax.js → AdminDashboard-hI5-kKpL.js} +2 -2
  117. package/dashboard/dist/assets/{AdminDashboard-BbWve-Ax.js.map → AdminDashboard-hI5-kKpL.js.map} +1 -1
  118. package/dashboard/dist/assets/{AgentConfigPage-M-9eTKhP.js → AgentConfigPage-DetU4HZe.js} +2 -2
  119. package/dashboard/dist/assets/{AgentConfigPage-M-9eTKhP.js.map → AgentConfigPage-DetU4HZe.js.map} +1 -1
  120. package/dashboard/dist/assets/{AgentDetailPage-CsakSlx5.js → AgentDetailPage-DrGXo8KQ.js} +3 -3
  121. package/dashboard/dist/assets/{AgentDetailPage-CsakSlx5.js.map → AgentDetailPage-DrGXo8KQ.js.map} +1 -1
  122. package/dashboard/dist/assets/{AgentsPage-Cj_cRTT1.js → AgentsPage-D4md5BzM.js} +2 -2
  123. package/dashboard/dist/assets/{AgentsPage-Cj_cRTT1.js.map → AgentsPage-D4md5BzM.js.map} +1 -1
  124. package/dashboard/dist/assets/{AvailableEscalationsPage-BXJmMBM1.js → AvailableEscalationsPage-B2bS2ZJy.js} +2 -2
  125. package/dashboard/dist/assets/{AvailableEscalationsPage-BXJmMBM1.js.map → AvailableEscalationsPage-B2bS2ZJy.js.map} +1 -1
  126. package/dashboard/dist/assets/{BotPicker-CGB16rYU.js → BotPicker-BXtey255.js} +2 -2
  127. package/dashboard/dist/assets/{BotPicker-CGB16rYU.js.map → BotPicker-BXtey255.js.map} +1 -1
  128. package/dashboard/dist/assets/{BulkAssignModal-MjxcU7N1.js → BulkAssignModal-CSL_PeZv.js} +2 -2
  129. package/dashboard/dist/assets/{BulkAssignModal-MjxcU7N1.js.map → BulkAssignModal-CSL_PeZv.js.map} +1 -1
  130. package/dashboard/dist/assets/CapabilitiesPage-5scufTHW.js +2 -0
  131. package/dashboard/dist/assets/{CapabilitiesPage-BxadotXn.js.map → CapabilitiesPage-5scufTHW.js.map} +1 -1
  132. package/dashboard/dist/assets/{CollapsibleSection-BBNpP-6h.js → CollapsibleSection-DjjDGxP_.js} +2 -2
  133. package/dashboard/dist/assets/{CollapsibleSection-BBNpP-6h.js.map → CollapsibleSection-DjjDGxP_.js.map} +1 -1
  134. package/dashboard/dist/assets/{ConfirmDeleteModal-D_kAbT_D.js → ConfirmDeleteModal-DJSmsHQM.js} +2 -2
  135. package/dashboard/dist/assets/{ConfirmDeleteModal-D_kAbT_D.js.map → ConfirmDeleteModal-DJSmsHQM.js.map} +1 -1
  136. package/dashboard/dist/assets/CountdownTimer-C5ZRYPfF.js +2 -0
  137. package/dashboard/dist/assets/{CountdownTimer-CI_JOAvk.js.map → CountdownTimer-C5ZRYPfF.js.map} +1 -1
  138. package/dashboard/dist/assets/{CredentialsPage-C9OA1Q2J.js → CredentialsPage-CV6e60yC.js} +2 -2
  139. package/dashboard/dist/assets/{CredentialsPage-C9OA1Q2J.js.map → CredentialsPage-CV6e60yC.js.map} +1 -1
  140. package/dashboard/dist/assets/{CronLabel-sxLLBf2I.js → CronLabel-CnqhqW0B.js} +2 -2
  141. package/dashboard/dist/assets/{CronLabel-sxLLBf2I.js.map → CronLabel-CnqhqW0B.js.map} +1 -1
  142. package/dashboard/dist/assets/{ElapsedCell-jH9m8Dgl.js → ElapsedCell-C1IOn-tj.js} +2 -2
  143. package/dashboard/dist/assets/{ElapsedCell-jH9m8Dgl.js.map → ElapsedCell-C1IOn-tj.js.map} +1 -1
  144. package/dashboard/dist/assets/{EntityTimelinePanel-Dl3Nr2kk.js → EntityTimelinePanel-Dz6Liig4.js} +2 -2
  145. package/dashboard/dist/assets/{EntityTimelinePanel-Dl3Nr2kk.js.map → EntityTimelinePanel-Dz6Liig4.js.map} +1 -1
  146. package/dashboard/dist/assets/{EscalationListSchemaPage-DAK6It5J.js → EscalationListSchemaPage-bgdmLmrh.js} +3 -3
  147. package/dashboard/dist/assets/{EscalationListSchemaPage-DAK6It5J.js.map → EscalationListSchemaPage-bgdmLmrh.js.map} +1 -1
  148. package/dashboard/dist/assets/{EscalationListView-CAHbdxKR.js → EscalationListView-CrlD3ZGt.js} +2 -2
  149. package/dashboard/dist/assets/{EscalationListView-CAHbdxKR.js.map → EscalationListView-CrlD3ZGt.js.map} +1 -1
  150. package/dashboard/dist/assets/{EscalationSchemaPage-DotfAMCL.js → EscalationSchemaPage-dxXO_T3t.js} +3 -3
  151. package/dashboard/dist/assets/{EscalationSchemaPage-DotfAMCL.js.map → EscalationSchemaPage-dxXO_T3t.js.map} +1 -1
  152. package/dashboard/dist/assets/{EscalationTimeline-DqmFydnU.js → EscalationTimeline-C3WQZQuM.js} +2 -2
  153. package/dashboard/dist/assets/{EscalationTimeline-DqmFydnU.js.map → EscalationTimeline-C3WQZQuM.js.map} +1 -1
  154. package/dashboard/dist/assets/{EscalationTitleSelect-BpRquguO.js → EscalationTitleSelect-CTtcETUO.js} +2 -2
  155. package/dashboard/dist/assets/{EscalationTitleSelect-BpRquguO.js.map → EscalationTitleSelect-CTtcETUO.js.map} +1 -1
  156. package/dashboard/dist/assets/{EscalationsOverview-DtGOqnP4.js → EscalationsOverview-DAjZ7JY2.js} +2 -2
  157. package/dashboard/dist/assets/{EscalationsOverview-DtGOqnP4.js.map → EscalationsOverview-DAjZ7JY2.js.map} +1 -1
  158. package/dashboard/dist/assets/{EventTable-DSHqKyX2.js → EventTable-B72DQP_v.js} +2 -2
  159. package/dashboard/dist/assets/{EventTable-DSHqKyX2.js.map → EventTable-B72DQP_v.js.map} +1 -1
  160. package/dashboard/dist/assets/{FilterBar-Dxd0wNSW.js → FilterBar-CCTaA_LH.js} +2 -2
  161. package/dashboard/dist/assets/{FilterBar-Dxd0wNSW.js.map → FilterBar-CCTaA_LH.js.map} +1 -1
  162. package/dashboard/dist/assets/GrantScopeField-BzWGzBfA.js +2 -0
  163. package/dashboard/dist/assets/GrantScopeField-BzWGzBfA.js.map +1 -0
  164. package/dashboard/dist/assets/{GraphInvokePage-ChqqGkro.js → GraphInvokePage-CAsCgFo3.js} +2 -2
  165. package/dashboard/dist/assets/{GraphInvokePage-ChqqGkro.js.map → GraphInvokePage-CAsCgFo3.js.map} +1 -1
  166. package/dashboard/dist/assets/{HomePage-ByKesG8w.js → HomePage-DseE_iSr.js} +2 -2
  167. package/dashboard/dist/assets/{HomePage-ByKesG8w.js.map → HomePage-DseE_iSr.js.map} +1 -1
  168. package/dashboard/dist/assets/{ListToolbar-Ddz_S2av.js → ListToolbar-1zdqnjW6.js} +2 -2
  169. package/dashboard/dist/assets/{ListToolbar-Ddz_S2av.js.map → ListToolbar-1zdqnjW6.js.map} +1 -1
  170. package/dashboard/dist/assets/{McpOverview-CVe13tEv.js → McpOverview-DS_JXDW5.js} +2 -2
  171. package/dashboard/dist/assets/{McpOverview-CVe13tEv.js.map → McpOverview-DS_JXDW5.js.map} +1 -1
  172. package/dashboard/dist/assets/{McpQueryDetailPage-BQYF2Mjq.js → McpQueryDetailPage-BeMUGcv3.js} +2 -2
  173. package/dashboard/dist/assets/{McpQueryDetailPage-BQYF2Mjq.js.map → McpQueryDetailPage-BeMUGcv3.js.map} +1 -1
  174. package/dashboard/dist/assets/{McpQueryPage-DZi5lm7s.js → McpQueryPage-CeiLO_LR.js} +2 -2
  175. package/dashboard/dist/assets/{McpQueryPage-DZi5lm7s.js.map → McpQueryPage-CeiLO_LR.js.map} +1 -1
  176. package/dashboard/dist/assets/{McpRunDetailPage-DreARTE1.js → McpRunDetailPage-DYp1uZK2.js} +2 -2
  177. package/dashboard/dist/assets/{McpRunDetailPage-DreARTE1.js.map → McpRunDetailPage-DYp1uZK2.js.map} +1 -1
  178. package/dashboard/dist/assets/{McpRunsPage-DmO0YVhT.js → McpRunsPage-VeaDzcL4.js} +2 -2
  179. package/dashboard/dist/assets/{McpRunsPage-DmO0YVhT.js.map → McpRunsPage-VeaDzcL4.js.map} +1 -1
  180. package/dashboard/dist/assets/{OperationsPage-DpjCO85T.js → OperationsPage-DK2mgaXf.js} +2 -2
  181. package/dashboard/dist/assets/{OperationsPage-DpjCO85T.js.map → OperationsPage-DK2mgaXf.js.map} +1 -1
  182. package/dashboard/dist/assets/{OperatorDashboard-Vv_MEBkv.js → OperatorDashboard-BNjLF0f7.js} +2 -2
  183. package/dashboard/dist/assets/{OperatorDashboard-Vv_MEBkv.js.map → OperatorDashboard-BNjLF0f7.js.map} +1 -1
  184. package/dashboard/dist/assets/{PersonaDetailPage-CW-2uBHY.js → PersonaDetailPage-IQ2fZLa6.js} +2 -2
  185. package/dashboard/dist/assets/{PersonaDetailPage-CW-2uBHY.js.map → PersonaDetailPage-IQ2fZLa6.js.map} +1 -1
  186. package/dashboard/dist/assets/{PersonasPage-CO6yYQdm.js → PersonasPage-DpGFEcU-.js} +2 -2
  187. package/dashboard/dist/assets/{PersonasPage-CO6yYQdm.js.map → PersonasPage-DpGFEcU-.js.map} +1 -1
  188. package/dashboard/dist/assets/{PortalPage-DcNqhBlg.js → PortalPage-D07xyvG_.js} +2 -2
  189. package/dashboard/dist/assets/{PortalPage-DcNqhBlg.js.map → PortalPage-D07xyvG_.js.map} +1 -1
  190. package/dashboard/dist/assets/{ProcessDetailPage-BuipGZcC.js → ProcessDetailPage-t8EEYSMk.js} +2 -2
  191. package/dashboard/dist/assets/{ProcessDetailPage-BuipGZcC.js.map → ProcessDetailPage-t8EEYSMk.js.map} +1 -1
  192. package/dashboard/dist/assets/{ProcessesListPage-D9DYntvl.js → ProcessesListPage-BDUXLFdb.js} +2 -2
  193. package/dashboard/dist/assets/{ProcessesListPage-D9DYntvl.js.map → ProcessesListPage-BDUXLFdb.js.map} +1 -1
  194. package/dashboard/dist/assets/ResolverForm-BwWOYv_Z.js +8 -0
  195. package/dashboard/dist/assets/ResolverForm-BwWOYv_Z.js.map +1 -0
  196. package/dashboard/dist/assets/RoleDetailPage-zMF3x2xi.js +8 -0
  197. package/dashboard/dist/assets/RoleDetailPage-zMF3x2xi.js.map +1 -0
  198. package/dashboard/dist/assets/{RolesPage-0QJOBSzF.js → RolesPage-CEBp_PpL.js} +2 -2
  199. package/dashboard/dist/assets/{RolesPage-0QJOBSzF.js.map → RolesPage-CEBp_PpL.js.map} +1 -1
  200. package/dashboard/dist/assets/{RunAsSelector-Dh4lyyRo.js → RunAsSelector-CC_EHN3z.js} +2 -2
  201. package/dashboard/dist/assets/{RunAsSelector-Dh4lyyRo.js.map → RunAsSelector-CC_EHN3z.js.map} +1 -1
  202. package/dashboard/dist/assets/ScanCodesPage-DD4uU-jY.js +2 -0
  203. package/dashboard/dist/assets/ScanCodesPage-DD4uU-jY.js.map +1 -0
  204. package/dashboard/dist/assets/ScanSchemeDetailPage-CUW8gYFh.js +2 -0
  205. package/dashboard/dist/assets/ScanSchemeDetailPage-CUW8gYFh.js.map +1 -0
  206. package/dashboard/dist/assets/ScanStationPage-DpO_ESuf.js +2 -0
  207. package/dashboard/dist/assets/ScanStationPage-DpO_ESuf.js.map +1 -0
  208. package/dashboard/dist/assets/{StickyPagination-BSUyf5ja.js → StickyPagination-cNqSDm5f.js} +2 -2
  209. package/dashboard/dist/assets/{StickyPagination-BSUyf5ja.js.map → StickyPagination-cNqSDm5f.js.map} +1 -1
  210. package/dashboard/dist/assets/{StreamMessageDetail--tp7l2Sd.js → StreamMessageDetail-C4oZqQdB.js} +2 -2
  211. package/dashboard/dist/assets/{StreamMessageDetail--tp7l2Sd.js.map → StreamMessageDetail-C4oZqQdB.js.map} +1 -1
  212. package/dashboard/dist/assets/{SwimlaneTimeline-CxBZ2THh.js → SwimlaneTimeline-BjBiV7GT.js} +2 -2
  213. package/dashboard/dist/assets/{SwimlaneTimeline-CxBZ2THh.js.map → SwimlaneTimeline-BjBiV7GT.js.map} +1 -1
  214. package/dashboard/dist/assets/{TaskDetailPage-BJ1QrmPH.js → TaskDetailPage-pO_AnVS1.js} +2 -2
  215. package/dashboard/dist/assets/{TaskDetailPage-BJ1QrmPH.js.map → TaskDetailPage-pO_AnVS1.js.map} +1 -1
  216. package/dashboard/dist/assets/{TasksListPage-5BydGWmt.js → TasksListPage-DKDHlWP7.js} +2 -2
  217. package/dashboard/dist/assets/{TasksListPage-5BydGWmt.js.map → TasksListPage-DKDHlWP7.js.map} +1 -1
  218. package/dashboard/dist/assets/{TimeAgo-CCnz84Y4.js → TimeAgo-D2qchUuJ.js} +2 -2
  219. package/dashboard/dist/assets/{TimeAgo-CCnz84Y4.js.map → TimeAgo-D2qchUuJ.js.map} +1 -1
  220. package/dashboard/dist/assets/{TimestampCell-D0ru7Lya.js → TimestampCell-DeswZGGo.js} +2 -2
  221. package/dashboard/dist/assets/{TimestampCell-D0ru7Lya.js.map → TimestampCell-DeswZGGo.js.map} +1 -1
  222. package/dashboard/dist/assets/{ToolTestPanel-3Kpofmen.js → ToolTestPanel-DJh7PIUa.js} +2 -2
  223. package/dashboard/dist/assets/{ToolTestPanel-3Kpofmen.js.map → ToolTestPanel-DJh7PIUa.js.map} +1 -1
  224. package/dashboard/dist/assets/{TopicDetailPage-BaBXD_AK.js → TopicDetailPage-CsgDJ4Z4.js} +2 -2
  225. package/dashboard/dist/assets/{TopicDetailPage-BaBXD_AK.js.map → TopicDetailPage-CsgDJ4Z4.js.map} +1 -1
  226. package/dashboard/dist/assets/{TopicsPage-Cnm1AAjh.js → TopicsPage-tBRZDVDN.js} +2 -2
  227. package/dashboard/dist/assets/{TopicsPage-Cnm1AAjh.js.map → TopicsPage-tBRZDVDN.js.map} +1 -1
  228. package/dashboard/dist/assets/{UserName-CXsdPiMZ.js → UserName-B-5JcDck.js} +2 -2
  229. package/dashboard/dist/assets/{UserName-CXsdPiMZ.js.map → UserName-B-5JcDck.js.map} +1 -1
  230. package/dashboard/dist/assets/{WorkflowExecutionPage-zx3AUJFd.js → WorkflowExecutionPage-BPHVnhp6.js} +2 -2
  231. package/dashboard/dist/assets/{WorkflowExecutionPage-zx3AUJFd.js.map → WorkflowExecutionPage-BPHVnhp6.js.map} +1 -1
  232. package/dashboard/dist/assets/{WorkflowsDashboard-DfAGK1l7.js → WorkflowsDashboard-CxLlfZmY.js} +2 -2
  233. package/dashboard/dist/assets/{WorkflowsDashboard-DfAGK1l7.js.map → WorkflowsDashboard-CxLlfZmY.js.map} +1 -1
  234. package/dashboard/dist/assets/{WorkflowsOverview-QIYvoa13.js → WorkflowsOverview-C4az5rwp.js} +2 -2
  235. package/dashboard/dist/assets/{WorkflowsOverview-QIYvoa13.js.map → WorkflowsOverview-C4az5rwp.js.map} +1 -1
  236. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DGgO3hyB.js → YamlWorkflowDetailPage-BmhlAUFQ.js} +8 -8
  237. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DGgO3hyB.js.map → YamlWorkflowDetailPage-BmhlAUFQ.js.map} +1 -1
  238. package/dashboard/dist/assets/{YamlWorkflowsPage-CEsHm0B9.js → YamlWorkflowsPage-CSbG5o2V.js} +2 -2
  239. package/dashboard/dist/assets/{YamlWorkflowsPage-CEsHm0B9.js.map → YamlWorkflowsPage-CSbG5o2V.js.map} +1 -1
  240. package/dashboard/dist/assets/{agents-v_HH3Rji.js → agents-BohEn7vw.js} +2 -2
  241. package/dashboard/dist/assets/{agents-v_HH3Rji.js.map → agents-BohEn7vw.js.map} +1 -1
  242. package/dashboard/dist/assets/{bots-bwvWYrXz.js → bots-BG2EO-Wi.js} +2 -2
  243. package/dashboard/dist/assets/{bots-bwvWYrXz.js.map → bots-BG2EO-Wi.js.map} +1 -1
  244. package/dashboard/dist/assets/{capabilities-D4vaw_nQ.js → capabilities-CeFrBmYX.js} +2 -2
  245. package/dashboard/dist/assets/{capabilities-D4vaw_nQ.js.map → capabilities-CeFrBmYX.js.map} +1 -1
  246. package/dashboard/dist/assets/{controlplane-CiiZ6LJF.js → controlplane-Dhvt8l8F.js} +2 -2
  247. package/dashboard/dist/assets/{controlplane-CiiZ6LJF.js.map → controlplane-Dhvt8l8F.js.map} +1 -1
  248. package/dashboard/dist/assets/{escalation-columns-Dv-MB3eC.js → escalation-columns-BWMNxImp.js} +2 -2
  249. package/dashboard/dist/assets/{escalation-columns-Dv-MB3eC.js.map → escalation-columns-BWMNxImp.js.map} +1 -1
  250. package/dashboard/dist/assets/{index-CzjUgFt_.js → index-Bd_dgyfJ.js} +2 -2
  251. package/dashboard/dist/assets/{index-CzjUgFt_.js.map → index-Bd_dgyfJ.js.map} +1 -1
  252. package/dashboard/dist/assets/{index-IJmtbuLh.js → index-C8VlM2Oe.js} +2 -2
  253. package/dashboard/dist/assets/{index-IJmtbuLh.js.map → index-C8VlM2Oe.js.map} +1 -1
  254. package/dashboard/dist/assets/{index-CcuvGdvf.js → index-CiqSVDga.js} +2 -2
  255. package/dashboard/dist/assets/{index-CcuvGdvf.js.map → index-CiqSVDga.js.map} +1 -1
  256. package/dashboard/dist/assets/{index-C3WutFpT.js → index-Cl_YXEdp.js} +2 -2
  257. package/dashboard/dist/assets/{index-C3WutFpT.js.map → index-Cl_YXEdp.js.map} +1 -1
  258. package/dashboard/dist/assets/index-CtZWpy22.js +2 -0
  259. package/dashboard/dist/assets/index-CtZWpy22.js.map +1 -0
  260. package/dashboard/dist/assets/{index-CjenRVX_.js → index-D90D9y0J.js} +2 -2
  261. package/dashboard/dist/assets/{index-CjenRVX_.js.map → index-D90D9y0J.js.map} +1 -1
  262. package/dashboard/dist/assets/{index-BLB77tc8.js → index-DABkM7s-.js} +2 -2
  263. package/dashboard/dist/assets/{index-BLB77tc8.js.map → index-DABkM7s-.js.map} +1 -1
  264. package/dashboard/dist/assets/index-Dbwu51xm.js +3 -0
  265. package/dashboard/dist/assets/index-Dbwu51xm.js.map +1 -0
  266. package/dashboard/dist/assets/{index-2tzsxFat.js → index-Dc3Om6G3.js} +2 -2
  267. package/dashboard/dist/assets/{index-2tzsxFat.js.map → index-Dc3Om6G3.js.map} +1 -1
  268. package/dashboard/dist/assets/{index-D2VKBMng.js → index-Dc_zWK23.js} +2 -2
  269. package/dashboard/dist/assets/{index-D2VKBMng.js.map → index-Dc_zWK23.js.map} +1 -1
  270. package/dashboard/dist/assets/index-DcppjzP5.css +1 -0
  271. package/dashboard/dist/assets/{index-C_ISDOWJ.js → index-Y-bdW8vo.js} +2 -2
  272. package/dashboard/dist/assets/{index-C_ISDOWJ.js.map → index-Y-bdW8vo.js.map} +1 -1
  273. package/dashboard/dist/assets/{index-C4Lce2ET.js → index-dYKgd06u.js} +3 -3
  274. package/dashboard/dist/assets/{index-C4Lce2ET.js.map → index-dYKgd06u.js.map} +1 -1
  275. package/dashboard/dist/assets/index-fUsx_gYx.js +67 -0
  276. package/dashboard/dist/assets/index-fUsx_gYx.js.map +1 -0
  277. package/dashboard/dist/assets/{knowledge-D1YP2NzS.js → knowledge-CCcTidNV.js} +2 -2
  278. package/dashboard/dist/assets/{knowledge-D1YP2NzS.js.map → knowledge-CCcTidNV.js.map} +1 -1
  279. package/dashboard/dist/assets/{mcp-Bb5psC61.js → mcp-CLA1z7Pg.js} +2 -2
  280. package/dashboard/dist/assets/{mcp-Bb5psC61.js.map → mcp-CLA1z7Pg.js.map} +1 -1
  281. package/dashboard/dist/assets/{mcp-query-D5jUdZwN.js → mcp-query-CWG0WMAs.js} +2 -2
  282. package/dashboard/dist/assets/{mcp-query-D5jUdZwN.js.map → mcp-query-CWG0WMAs.js.map} +1 -1
  283. package/dashboard/dist/assets/metadata-display-B7X5hGxG.js +2 -0
  284. package/dashboard/dist/assets/metadata-display-B7X5hGxG.js.map +1 -0
  285. package/dashboard/dist/assets/{personas-CDndbkp4.js → personas-8jURIzx6.js} +2 -2
  286. package/dashboard/dist/assets/{personas-CDndbkp4.js.map → personas-8jURIzx6.js.map} +1 -1
  287. package/dashboard/dist/assets/{pipelines-Dq3kNhKu.js → pipelines-hVVRAXqG.js} +2 -2
  288. package/dashboard/dist/assets/{pipelines-Dq3kNhKu.js.map → pipelines-hVVRAXqG.js.map} +1 -1
  289. package/dashboard/dist/assets/{tasks-BlpDsWCy.js → tasks-BE1O57Bf.js} +2 -2
  290. package/dashboard/dist/assets/{tasks-BlpDsWCy.js.map → tasks-BE1O57Bf.js.map} +1 -1
  291. package/dashboard/dist/assets/{topics-C0YaR9fu.js → topics-Bg96Wp1r.js} +2 -2
  292. package/dashboard/dist/assets/{topics-C0YaR9fu.js.map → topics-Bg96Wp1r.js.map} +1 -1
  293. package/dashboard/dist/assets/{useDebouncedValue-B-KC5ba3.js → useDebouncedValue-DmEveyEy.js} +2 -2
  294. package/dashboard/dist/assets/{useDebouncedValue-B-KC5ba3.js.map → useDebouncedValue-DmEveyEy.js.map} +1 -1
  295. package/dashboard/dist/assets/{useNamespace-Dkr5g2Ac.js → useNamespace-DHgkzIuL.js} +2 -2
  296. package/dashboard/dist/assets/{useNamespace-Dkr5g2Ac.js.map → useNamespace-DHgkzIuL.js.map} +1 -1
  297. package/dashboard/dist/assets/{useYamlActivityEvents-BANBOshA.js → useYamlActivityEvents-RU0PvU8S.js} +2 -2
  298. package/dashboard/dist/assets/{useYamlActivityEvents-BANBOshA.js.map → useYamlActivityEvents-RU0PvU8S.js.map} +1 -1
  299. package/dashboard/dist/assets/{yaml-workflows-BLYoJfmB.js → yaml-workflows-DQWE9ijc.js} +2 -2
  300. package/dashboard/dist/assets/{yaml-workflows-BLYoJfmB.js.map → yaml-workflows-DQWE9ijc.js.map} +1 -1
  301. package/dashboard/dist/index.html +2 -2
  302. package/docs/dashboard.md +1 -1
  303. package/docs/hitl/lookups.md +27 -18
  304. package/docs/hitl/x-lt-scan.md +51 -0
  305. package/docs/hitl/x-lt-validation.md +1 -1
  306. package/docs/hitl/x-lt-widget.md +1 -1
  307. package/docs/hitl-guide.md +3 -1
  308. package/docs/scan-codes.md +303 -33
  309. package/package.json +1 -1
  310. package/build/types/scan-code.d.ts +0 -317
  311. package/dashboard/dist/assets/CapabilitiesPage-BxadotXn.js +0 -2
  312. package/dashboard/dist/assets/CountdownTimer-CI_JOAvk.js +0 -2
  313. package/dashboard/dist/assets/ResolverForm-BVG8X0sS.js +0 -8
  314. package/dashboard/dist/assets/ResolverForm-BVG8X0sS.js.map +0 -1
  315. package/dashboard/dist/assets/RoleDetailPage-KCXDqoJz.js +0 -8
  316. package/dashboard/dist/assets/RoleDetailPage-KCXDqoJz.js.map +0 -1
  317. package/dashboard/dist/assets/ScanCodesPage-CUhCz-Yr.js +0 -2
  318. package/dashboard/dist/assets/ScanCodesPage-CUhCz-Yr.js.map +0 -1
  319. package/dashboard/dist/assets/ScanSchemeDetailPage-qzXKNqJK.js +0 -2
  320. package/dashboard/dist/assets/ScanSchemeDetailPage-qzXKNqJK.js.map +0 -1
  321. package/dashboard/dist/assets/ScanStationPage-D8e8cG3t.js +0 -2
  322. package/dashboard/dist/assets/ScanStationPage-D8e8cG3t.js.map +0 -1
  323. package/dashboard/dist/assets/index-CT-FN6CW.js +0 -3
  324. package/dashboard/dist/assets/index-CT-FN6CW.js.map +0 -1
  325. package/dashboard/dist/assets/index-D6pBLZFu.js +0 -2
  326. package/dashboard/dist/assets/index-D6pBLZFu.js.map +0 -1
  327. package/dashboard/dist/assets/index-Dh8MJl3L.css +0 -1
  328. package/dashboard/dist/assets/index-N71QQ3J5.js +0 -67
  329. package/dashboard/dist/assets/index-N71QQ3J5.js.map +0 -1
  330. package/dashboard/dist/assets/metadata-display-COo3mMCr.js +0 -2
  331. package/dashboard/dist/assets/metadata-display-COo3mMCr.js.map +0 -1
@@ -23,6 +23,7 @@ canonical action surface. A scan is an **ECA rule** over that surface:
23
23
  - [Rules and steps](#rules-and-steps)
24
24
  - [Confirmation](#confirmation)
25
25
  - [Info-choice screens](#info-choice-screens)
26
+ - [Holding an item: the bench motion](#holding-an-item-the-bench-motion)
26
27
  - [Identity schemes and acting identity](#identity-schemes-and-acting-identity)
27
28
  - [Station deployments](#station-deployments)
28
29
  - [The fallback screen](#the-fallback-screen)
@@ -31,6 +32,7 @@ canonical action surface. A scan is an **ECA rule** over that surface:
31
32
  - [Scanner setup](#scanner-setup)
32
33
  - [Admin configuration](#admin-configuration)
33
34
  - [The printer demo](#the-printer-demo)
35
+ - [The bag and bin demo](#the-bag-and-bin-demo)
34
36
 
35
37
  ## The code
36
38
 
@@ -62,6 +64,14 @@ A scheme (`lt_config_scan_schemes`, indexed 10–99) declares:
62
64
  `1015949975930`. Fits UPC-A/EAN/ITF labels; a trailing check digit is
63
65
  accepted. Two digits of version + one of category + `target_length`
64
66
  digits of target.
67
+ - `gtin` — manufacturer barcodes: UPC-A, EAN-13, EAN-8 and GTIN-14, as
68
+ printed on products. The code carries no version or category; a valid
69
+ check digit identifies it. The target is the 14-digit GTIN (a UPC-A
70
+ `036000291452` targets `00036000291452`), `{scan.code}` keeps the code as
71
+ scanned, and the scheme's one rule is category `0`. A deployment has at
72
+ most one gtin scheme, and while it is enabled no enabled fixed scheme may
73
+ accept an 8, 12, 13 or 14 digit code: the scheme write is refused, naming
74
+ both schemes, so a digits-only code always has exactly one reading.
65
75
 
66
76
  ## Rules and steps
67
77
 
@@ -78,7 +88,8 @@ Each step:
78
88
  "roles": ["printer-fleet"], // expected queue(s)
79
89
  "status": "pending", // pending | resolved | cancelled
80
90
  "availability": "available", // available | claimed | mine | any
81
- "facets": { "state": "printing" } // extra metadata guards
91
+ "facets": { "state": "printing" }, // extra metadata guards
92
+ "subtypes": ["open"] // optional: types / subtypes the row must have
82
93
  },
83
94
  "cardinality": "first", // first | many
84
95
  "verb": "resolve",
@@ -87,6 +98,14 @@ Each step:
87
98
  }
88
99
  ```
89
100
 
101
+ `query.types` and `query.subtypes` narrow steps that locate a row and then
102
+ act on it by id: `show-detail`, `show-list`, `present` (with any choice; each
103
+ choice writes the row shown), `hold`, `fill`, and `accumulate`
104
+ (container-locate mode narrows the container). Claim, cancel, release,
105
+ resolve and escalate steps locate by the target facet inside their atomic
106
+ statement, so the upsert refuses types/subtypes on them; to narrow one, offer
107
+ it as a `present` choice. With `availability: "mine"` each takes one entry.
108
+
90
109
  Verbs are the canonical escalation actions:
91
110
 
92
111
  | Verb | Effect | Params |
@@ -111,8 +130,8 @@ Verbs are the canonical escalation actions:
111
130
 
112
131
  A `resolve` step applies it inside the atomic resolve: `mine` resolves the
113
132
  actor's own claim, the most recently claimed first when there are several,
114
- so a shelf scan bins the bag in the associate's hand even when another bag
115
- for the same shelf is waiting. With no matching row the step falls through.
133
+ so a container scan places the item in the associate's hand even when
134
+ another item for the same container is waiting. With no matching row the step falls through.
116
135
 
117
136
  String values inside `itemKey`, `resolverPayload`, and `metadata` interpolate
118
137
  `{scan.target}`, `{scan.category}`, and `{scan.scannedAt}`. Two facet bags
@@ -121,15 +140,15 @@ extend them: `{claim.<facet>}` reads the acting user's single live claim
121
140
  make the step fall through), and `{item.<facet>}` reads the row an item-mode
122
141
  `accumulate` step located. A token that cannot resolve falls through instead
123
142
  of writing the literal, and the rule editor rejects `{item.…}` on any other
124
- step. A bag-first ladder therefore names the order from the claim:
125
- `"itemKey": "{claim.orderId}"` on the shelf scan. Every mutating verb stamps
143
+ step. An item-first ladder therefore names the item from the claim:
144
+ `"itemKey": "{claim.itemId}"` on the container scan. Every mutating verb stamps
126
145
  provenance facets onto the row it touches — `scanScheme`, `scanCategory`,
127
146
  `scanActionName`, `scannedAt` — so scan-driven transitions stay queryable.
128
147
 
129
148
  `accumulate` has two modes. With `params.accumulate.containerFacet` the
130
149
  scanned target is the ITEM: the step locates the item's own pending row
131
- through the scheme facet, reads that facet from the row (a bag row carrying
132
- `binKey: "B-7"`), and adds the target to the pending accumulator whose
150
+ through the scheme facet, reads that facet from the row (an item row carrying
151
+ `containerKey: "C-7"`), and adds the target to the pending accumulator whose
133
152
  metadata carries the same value, writing the item's row as the reciprocal in
134
153
  the same statement (`reciprocal: false` skips it). The container pick
135
154
  considers only pending rows that carry the accumulator declaration, so a
@@ -138,27 +157,27 @@ release row sharing the facet during a pack-out is never the target.
138
157
 
139
158
  ```jsonc
140
159
  "accumulate": {
141
- "containerFacet": "boxKey",
160
+ "containerFacet": "containerKey",
142
161
  "container": {
143
- "roles": ["match-filling"], // container queues (containerRoles is an alias)
144
- "types": ["matchBox"], // escalation type
145
- "subtypes": ["box"], // escalation subtype
162
+ "roles": ["packing"], // container queues (containerRoles is an alias)
163
+ "types": ["container"], // escalation type
164
+ "subtypes": ["open"], // escalation subtype
146
165
  "facets": { "open": true } // extra metadata guards
147
166
  }
148
167
  }
149
168
  ```
150
169
 
151
170
  A design that parks each item as its own accumulator in the same queue as
152
- its container (a bag's slot beside its box, both carrying `boxKey`) declares
153
- the container's type or subtype, so the add lands in the box and never in a
154
- sibling's slot. The item's own row is never its container. Without it the scanned target is the CONTAINER and the step
171
+ its container (an item's slot beside its container, both carrying
172
+ `containerKey`) declares the container's type or subtype, so the add lands in
173
+ the container and never in a sibling's slot. The item's own row is never its container. Without it the scanned target is the CONTAINER and the step
155
174
  adds `params.itemKey` (a template) to it. An item with no row or no container
156
175
  facet falls through to the next step; a container already holding the item
157
176
  reports a conflict. When the item's row exists but no pending container
158
177
  carries its facet (the previous container closed and its successor has not
159
178
  parked yet), the step answers `no_open_container` with the item row, the
160
179
  facet, and the rule's fallback markdown, so the station reads "Container
161
- closing, scan again" instead of the bag with no hint.
180
+ closing, scan again" instead of the item with no hint.
162
181
 
163
182
  Ordering is the power move: put the expected state first and a broad
164
183
  `show-detail` last. A machine whose twin is in the wrong queue still answers
@@ -182,7 +201,7 @@ guarded call every other surface uses.
182
201
 
183
202
  ## Info-choice screens
184
203
 
185
- Some objects carry one code for their whole life — an item tag, an order
204
+ Some objects carry one code for their whole life — an item tag, a record
186
205
  label. One code, many possible intents, and the right one depends on where
187
206
  the object is in its journey. The `present` verb closes that gap: the step
188
207
  locates the row, states its reality, and returns a configured, labeled
@@ -209,10 +228,32 @@ unsatisfied. Picking one calls `POST /api/scan-codes/execute-choice` with a
209
228
  pointer (scheme, category, step index, choice index, escalation id) — and a
210
229
  pointer is never authority: the server re-reads live config, re-locates the
211
230
  row under the step's query, re-applies the identity gate, and runs the verb
212
- through the same atomic executors a direct scan uses. A resolve choice
213
- resolves the exact row the screen presented, re-checked under the step's
214
- query in the same statement. A row that moved on between render and tap
215
- answers `conflict`, exactly as a lost double-scan.
231
+ through the same atomic executors a direct scan uses. Every choice writes
232
+ the exact row the screen presented, by id: claim, cancel, resolve and an
233
+ escalate's `closeCurrent` re-check that row (pending, the scanned code, the
234
+ step's roles, no live claim by someone else) in the same statement, and
235
+ release releases that row when it is the actor's claim. When one code names
236
+ several pending rows, the write never lands on a different one. A row that
237
+ moved on between render and tap answers `conflict`, exactly as a lost
238
+ double-scan.
239
+
240
+ A `present` step names the facts the station states about the row with
241
+ `facts`: up to 12 labeled templates, rendered server-side against the row
242
+ (`{item.<facet>}`) and the scan tokens, shown in order. A token the row cannot
243
+ fill renders as a dash.
244
+
245
+ ```jsonc
246
+ "facts": [
247
+ { "label": "Bin", "value": "{item.binCode}" },
248
+ { "label": "Clinic", "value": "{item.facilityName}" },
249
+ { "label": "Bags", "value": "{item.memberCount}" }
250
+ ]
251
+ ```
252
+
253
+ Without `facts`, the station lists the row's metadata, leaving out the
254
+ bookkeeping a bench never acts on: scan provenance (`scannedAt`,
255
+ `scanScheme`, `scanCategory`, `scanStation`, `scanActionName`),
256
+ `resolved_by`, `schema_version`, and the accumulate and batch counters.
216
257
 
217
258
  A choice's `code` is a short printable token (letters, digits, underscore,
218
259
  dash) enabling double-scan selection: scan the object, then scan an action
@@ -231,6 +272,153 @@ duration, form on screen. An unsatisfied identity requirement never
231
272
  auto-fires as the wrong actor — the scan stops over at the badge screen and
232
273
  completes on its own once a badge primes.
233
274
 
275
+ ## Holding an item: the bench motion
276
+
277
+ At a bench where items go into containers, every act is one item going into
278
+ one container, or one expected item checked off a record. The motion is three scans with nothing in between: **the item, your badge, the
279
+ container**. The container scan is the act. The item scan only says which
280
+ item; the badge says who.
281
+
282
+ **`hold`** is the item scan. The step locates the row and answers `held`
283
+ with a **subject**: the code that held it, the row id, a label, an expiry,
284
+ and what to scan next. It writes nothing and spends no badge use. The
285
+ station keeps the subject (device-local, in memory) and sends it back with
286
+ the next scans as `subject: { code, escalationId }`. Scanning another item
287
+ replaces it; it lapses on its own after `ttlSeconds` (default 45).
288
+
289
+ ```jsonc
290
+ { "query": { "roles": ["packing"] }, "verb": "hold",
291
+ "params": { "hold": { "ttlSeconds": 300, "label": "{scan.target}",
292
+ "headline": "{item.containerCode}", "subline": "{item.locationName}",
293
+ "expect": { "schemes": [14], "prompt": "Walk to this container and scan it.\n\n{item.labelNote}" } } } }
294
+ ```
295
+
296
+ **What the station shows.** With `hold.headline` the destination leads the
297
+ screen: the headline (e.g. `{item.containerCode}`) in very large type, `hold.subline`
298
+ under it (e.g. `{item.locationName}`), the held item itself as a quieter line
299
+ above, then the `expect.prompt`. A template that renders empty drops its line,
300
+ so a row-level note such as `{item.labelNote}` appears only when the workflow
301
+ set one ("Take the new label from the printer and stick it on C-12").
302
+ Any step that writes may carry `done: { markdown }`: the copy the station shows,
303
+ large, once the write lands and until the next scan ("Place it in
304
+ **{container.containerCode}**. All good."). It reads the same bags its refusal copy
305
+ reads. A fill or an into-subject add that leaves slots open shows its done
306
+ copy under the hold; without done copy the station shows the count ("2 of 5
307
+ checked off" for a fill, "2 of 5 added" for an add).
308
+
309
+ The server trusts nothing about the subject. Each request re-parses its
310
+ code, re-reads the row under the station's read scope, requires the row to
311
+ still carry the code's target and to be pending. A subject that fails is
312
+ stale: steps that need one are skipped, and when nothing else matches the
313
+ answer is `subject_stale` with `clearSubject: true`.
314
+
315
+ **Subject steps** run only while a subject from one of `subject.schemes` is
316
+ held, and act on it. The `{subject.<facet>}` template bag reads the held
317
+ row.
318
+
319
+ | Step | What the container scan does |
320
+ |---|---|
321
+ | `accumulate` with `accumulate.from: "subject"` | The held item joins the accumulator the scan names (found by the scheme facet, narrowed by `accumulate.container`). The held row is written as the reciprocal in the same statement, so a held row parked as a one-slot accumulator completes and its workflow wakes. `params.itemKey` names the item, e.g. `{subject.itemId}`. |
322
+ | `accumulate` with `accumulate.into: "subject"` | The held row collects the scanned code (`itemKey` defaults to `{scan.target}`). Use it when the container has no row yet: the first item for a container tells the workflow which container was scanned. With `accumulate.item`, the scanned code's own pending row is written as the reciprocal in the same statement (see below). On a bounded accumulator the answer carries `progress` and the subject stays held while slots remain. |
323
+ | `fill` with `fill.into: "subject"` | One expected item is checked off: the held row is a batch whose keys are codes, and the scan fills the first open key for its code. A code expected twice is declared `<code>#1`, `<code>#2` and takes two scans; a third is refused. The answer carries `progress: { filled, total, remaining }`, the subject stays held while items remain, and the last fill completes the row. On a `gtin` scheme a refusal's `expected` lists codes as the package prints them (EAN-13, or EAN-8), not the stored 14-digit form. `fill.into: "scanned"` fills the row the scan itself names. |
324
+
325
+ **`match` and `refuse`** check the pairing before anything is written.
326
+ `match.target` lists templates the scanned target must equal (e.g.
327
+ `["{subject.containerCode}"]`); `match.facets` lists facets the located container
328
+ must share with the held row (e.g. `["containerKey"]`). A miss answers `refused`
329
+ with `refuse.markdown` rendered, and nothing is written. The copy may read
330
+ `{container.<facet>}` (the container the scan named) and `{fill.pending}`
331
+ (what a fill still expects), so the station says what is wrong and where the
332
+ item goes: "That's Acme West's container. This one goes in **C-12**." When the
333
+ write itself loses a race (the container closed, another bench took the free
334
+ container), the answer is `refused` with `refuse.conflict`, and a step that
335
+ declares `refuse.conflict` also clears the subject so the next item scan
336
+ reads fresh state. Exactly one of two racing acts wins whenever both name
337
+ the same row: the reciprocal statement writes both rows or neither.
338
+
339
+ **The item's own row on an into-subject add.** A held container that collects
340
+ item codes (a box being packed from a bin) often has items that each wait on
341
+ their own row. `accumulate.item` locates that row and writes it as the
342
+ reciprocal of the container's entry, so the two land together or not at all:
343
+
344
+ ```jsonc
345
+ // 11:0, item label, while the box is held
346
+ { "query": { "roles": ["packing"], "status": "pending" }, "verb": "accumulate",
347
+ "requireActingIdentity": true, "subject": { "schemes": [14] },
348
+ "match": { "target": ["{subject.memberCodes}"] },
349
+ "refuse": { "markdown": "Not one of this bin's items.",
350
+ "missing": "That item is not waiting to be packed." },
351
+ "done": { "markdown": "{item.itemCode} is in. {container.accumulate_count} of {container.accumulate_max}." },
352
+ "params": { "itemKey": "{scan.target}",
353
+ "accumulate": { "into": "subject", "item": { "roles": ["ship"], "facets": { "shape": "consolidated" } } } } }
354
+ ```
355
+
356
+ The item row is a pending accumulator whose scheme facet equals the scanned
357
+ target (a plain row carrying the same code is not a candidate: only an
358
+ accumulator can take the reciprocal entry), in `item.roles` (intersected with the actor's read scope), narrowed by
359
+ `item.types`, `item.subtypes` and `item.facets`; the held row is never a
360
+ candidate. Parked as `max: 1`, it completes in the same statement and its
361
+ workflow wakes with the container's id in `$accumulated`. No such row answers
362
+ `refused` with `refuse.missing` (or falls through without it); two answer
363
+ `conflict`. An item already placed elsewhere is refused with the box still
364
+ held, an item scanned twice into it answers `already` with the box still
365
+ held, and a live claim on the item row by someone else refuses with their
366
+ name (checked before the write; the statement itself does not re-check it). `{item.<facet>}` reads the item row in `itemKey`, the payload,
367
+ metadata and copy; `{container.<facet>}` in the copy reads the held row as the
368
+ add left it.
369
+
370
+ **One rule, several kinds of held item.** `subject.facets` makes a step
371
+ apply only while the held row carries those facet values; otherwise the step
372
+ is skipped and the next one runs. A placing bench uses it to tell an item
373
+ whose destination already has a container from one that needs a free
374
+ container: the item row carries `placement: 'assigned'` or `'unassigned'`, and
375
+ the container label's rule has one step for each.
376
+
377
+ ```jsonc
378
+ // 14:0, container label
379
+ [
380
+ { "verb": "accumulate", "requireActingIdentity": true,
381
+ "subject": { "schemes": [11], "facets": { "placement": "assigned" } },
382
+ "match": { "target": ["{subject.containerCode}"] },
383
+ "refuse": { "markdown": "That's {container.locationName}'s container. This one goes in **{subject.containerCode}**." },
384
+ "params": { "itemKey": "{subject.itemId}",
385
+ "accumulate": { "from": "subject", "container": { "types": ["container"], "subtypes": ["open"] } } } },
386
+ { "verb": "accumulate", "requireActingIdentity": true,
387
+ "subject": { "schemes": [11], "facets": { "placement": "unassigned" } },
388
+ "match": { "target": ["{subject.offeredContainers}"] },
389
+ "refuse": { "markdown": "Choose a free container: {subject.offeredContainers}.",
390
+ "conflict": "That container was just taken. Scan the item again.",
391
+ "missing": "That container was just taken. Scan the item again." },
392
+ "params": { "itemKey": "{subject.itemId}",
393
+ "accumulate": { "from": "subject", "container": { "types": ["container"], "subtypes": ["free"] } } } },
394
+ { "query": { "roles": ["packing"] }, "verb": "present", "choices": [ /* Close this container, View container */ ] }
395
+ ]
396
+ ```
397
+
398
+ The workflow stamps the containers it offers on the item row (`offeredContainers`). A
399
+ `match.target` entry that is a lone token naming a list facet allows every
400
+ entry, so any offered container takes the item and any other is refused with
401
+ the offer listed. A free container is a `max: 1` accumulator row, so of two
402
+ items racing for one free container exactly one lands; the other is refused with the
403
+ conflict copy. `refuse.missing` covers an offered container with no open row (taken
404
+ a moment ago): the scan is refused instead of falling through to the next
405
+ step. The third step has no subject gate: scanning a container with no item
406
+ held offers to close it.
407
+
408
+ Someone else's live claim on the held row refuses the act with their name
409
+ (`subject.claimedByOther: "allow"` lifts this). An item already in the
410
+ scanned container answers `executed` with `already: true`, and nothing is
411
+ written twice.
412
+
413
+ The badge can come before the item or between the item and the container. A
414
+ container scan that needs a badge answers `not_primed` with
415
+ `replayable: true`; the station keeps the subject, asks for the badge, and
416
+ replays the container scan once the badge primes.
417
+
418
+ Subject steps sit beside the steps a rule already has: with no subject held
419
+ they are skipped, so the same rule keeps working for scans made without the
420
+ item scan first.
421
+
234
422
  ## Identity schemes and acting identity
235
423
 
236
424
  A scheme with `kind: "identity"` is the badge layer. Its `target_facet`
@@ -247,7 +435,39 @@ scheme's policy:
247
435
  | Scheme field | Meaning |
248
436
  |---|---|
249
437
  | `grant_ttl_seconds` | How long the grant lives (1–86400). |
250
- | `grant_max_uses` | `0` = TTL-bound; `n` = the grant covers n requests that carry it, scans and work verbs alike (a strict one-request policy is `1`). The primed response carries `maxUses`; a single-shot grant is retired on the device the moment its one request returns, and a scan that comes back `not_primed` while a grant is held drops that grant, so the next scan runs unprimed instead of repeating the badge screen. A badge-gated submit likewise retires its badge once the write lands. |
438
+ | `grant_max_uses` | `0` = TTL-bound; `n` = the grant covers n acts (a strict one-act policy is `1`). |
439
+ | `grant_scope` | `action` (default): each act spends one use. `subject`: the first act binds the grant to the held subject; further acts on that subject spend nothing, and the grant acts on no other subject. One badge then covers one item's whole motion (every part checked off one record) and never carries to the next item. |
440
+
441
+ **The station decides the policy.** One printed badge works at every bench,
442
+ but benches want different policies: a workstation grants one act per badge
443
+ scan (claim, then submit, takes two scans), a high-volume placing bench grants
444
+ ten minutes of acts. A role may declare its stations' policy in `properties.badge_grant`:
445
+
446
+ ```jsonc
447
+ "properties": { "kiosk": true, "badge_grant": { "ttl_seconds": 600, "max_uses": 0 } }
448
+ ```
449
+
450
+ A badge scanned on a device signed in as a member of that role mints under
451
+ it; fields left out keep the badge scheme's values (`scope` is the role's
452
+ `grant_scope`). A device that belongs to several roles with different
453
+ policies uses the role it is locked to (the dashboard sends its kiosk role as
454
+ `stationRole` with every scan); with no role policy, or no way to choose
455
+ between several, the badge scheme's own policy applies. The policy is
456
+ validated where the role is written: an unusable `badge_grant` is a 400 from
457
+ the role API and fails a code-owned role's startup apply. The role page edits
458
+ it under **Members → Badge at this station**.
459
+
460
+ A grant is spent by **acts**, not by looking. A scan that shows, presents,
461
+ holds, or refuses reads the grant without spending it; a mutating verb spends
462
+ one use just before it writes, and a write that does not land (the step
463
+ falls through, the scan is refused) gives the use back. Every scan response
464
+ that carried a grant reports `acting: { consumed, remaining, bound }`, and
465
+ the escalation work routes report the uses left in the
466
+ `X-LT-Acting-Remaining` header. The device retires its copy of the grant when
467
+ the server reports none left, or when a subject-bound grant's subject is
468
+ done; reads never carry the grant at all. A scan that comes back
469
+ `not_primed` while a grant is held drops that grant, so the next scan runs
470
+ unprimed instead of repeating the badge screen.
251
471
 
252
472
  The grant rides subsequent scans as `actingToken`. Verbs then run **as the
253
473
  badged person under their own live RBAC** — the grant confers attribution,
@@ -287,7 +507,10 @@ signed-in user is a **member of exactly that one role** — the station-login
287
507
  shape — the dashboard locks the viewport: the left nav is gone entirely, the
288
508
  role's escalation list is home (`/` and every other surface redirect to it),
289
509
  and the session is held to the list, the escalation detail page, the role's
290
- portals, and the scan screens (choice, badge). A role that declares
510
+ portals, and the scan screens (choice, badge). The list with neither a
511
+ `role` nor a `facets` filter is every queue at once, so it redirects home
512
+ too, including right after sign-in; a scan's `show-list` (narrowed by
513
+ facets) stays. A role that declares
291
514
  [portals](./hitl/portal.md) lands on its first one as home instead of the list. The header toolbar and event feed remain. A user
292
515
  holding more than one role, or an admin-type grant, always gets full chrome —
293
516
  kiosk is for the single-role floor login, never a way to hide the product from
@@ -313,12 +536,16 @@ submit opens a badge prompt naming that claimant. The resolve fires the moment
313
536
  a matching badge primes; a badge that is not the claimant's is named and the
314
537
  submit is held.
315
538
 
316
- The gate is on **use**, not on grant lifetime. Acting grants are single-use:
317
- the claim already spent the grant it was minted with, so the submit owes a
318
- fresh tap — every write consumes its own grant. A held token proves nothing
319
- about whether a submit can succeed, so the surface never lets one skip the
320
- badge. Editing persists nothing locally, so the identity gate sits at the one
321
- place a record actually changes, once per change.
539
+ The gate is on **use**, not on grant lifetime. A live grant that names the
540
+ claimant and has uses left carries the submit and spends one use, as a scan
541
+ act does (release, cancel and escalate always ask); the badge prompt shows only when no such grant is held. The station
542
+ holds a grant only while it has uses left, so a single-use badge spent by the
543
+ claim makes the submit ask for a fresh tap, while a role's
544
+ `badge_grant: { ttl_seconds: 600, max_uses: 0 }` lets one badge claim and
545
+ submit for ten minutes. A grant bound to a scanned subject (`grant_scope:
546
+ "subject"`) belongs to that scan and still asks. Editing persists nothing
547
+ locally, so the identity gate sits at the one place a record actually
548
+ changes, once per change.
322
549
 
323
550
  Mutations attribute to the badged person (`assigned_to`, `resolved_by`)
324
551
  with the device recorded beside them (`scanStation` in the scan
@@ -333,6 +560,12 @@ to continue. The badge scan primes the session and the pending action
333
560
  completes on its own. With a live grant the stop-over never appears — the
334
561
  identity layer is invisible when all is well.
335
562
 
563
+ A field can take a scan while the form is open: with
564
+ [`x-lt-scan`](hitl/x-lt-scan.md), scanning a container into a claimed form
565
+ fills its container field (checked against what the record expects), and
566
+ `x-lt-scan-submit` submits once the scan fields are filled, which raises the
567
+ badge prompt. A scan no field accepts runs globally as usual.
568
+
336
569
  Badge tokens are printed credentials: seed them as long random strings,
337
570
  bind them server-side (`lt_users.metadata`), and treat badge possession
338
571
  with the same physical policy as any badge system. Unknown badges answer
@@ -347,9 +580,10 @@ renders the markdown; a configured route navigates.
347
580
 
348
581
  ## Executing a scan
349
582
 
350
- `POST /api/scan-codes/execute` takes `{ "code": "10:1:SN-123" }` and runs
351
- as the calling user under normal RBAC. Every terminal state is a structured
352
- 200 outcome:
583
+ `POST /api/scan-codes/execute` takes `{ "code": "10:1:SN-123" }` (plus
584
+ `actingToken` and `subject` when the station holds them) and runs as the
585
+ calling user under normal RBAC. Every terminal state is a structured 200
586
+ outcome:
353
587
 
354
588
  | Outcome | Meaning |
355
589
  |---------|---------|
@@ -362,6 +596,15 @@ as the calling user under normal RBAC. Every terminal state is a structured
362
596
  | `invalid_code` | The string parses under no enabled scheme |
363
597
  | `forbidden` | The caller's roles bar the matched action |
364
598
  | `conflict` | A concurrent actor won the row |
599
+ | `choices` | A `present` step located its row; `escalation` + `choices` included |
600
+ | `held` | A `hold` step located its row; `subject` included |
601
+ | `refused` | The scan was understood and not acted on; nothing written; `refusal.markdown` (and `refusal.expected`) included |
602
+ | `subject_stale` | The held subject moved on; `clearSubject: true` |
603
+ | `not_primed` | The act needs a badge; `notPrimed` included, `replayable: true` when the scan may run again once one primes |
604
+ | `identity_primed` / `identity_unknown` | A badge scan matched a person, or did not |
605
+
606
+ A step's payload rejected by the role's form schema answers `refused` with
607
+ the reason, never a raw 422.
365
608
 
366
609
  The endpoint is source-agnostic — anything that produces a string can drive
367
610
  it: a barcode scanner, an RFID reader, a camera decode, an MCP tool
@@ -381,8 +624,18 @@ When enabled, the dashboard listens for scans globally — any page, any focus
381
624
  state. A scanner paired as an HID keyboard "types" its decode and finishes
382
625
  with Enter; capture is **pattern-anchored**: a capture-phase window listener
383
626
  accumulates keystrokes freely, and when the terminator arrives it checks
384
- whether the recent keys end with a scan-code shape
385
- (`[1-9][0-9]:[0-9]:target` or the digits-only fixed form).
627
+ whether the recent keys end with a scan-code shape. The shapes come from the
628
+ configured schemes: each delimited version's `VV:C:target`, each fixed
629
+ scheme's exact digit lengths, and for a gtin scheme a whole 8, 12, 13 or 14
630
+ digit run whose check digit holds (a leading zero is part of the code). A
631
+ code never starts inside a longer run of digits.
632
+
633
+ - **Digits-only codes fire only at scanner speed** (avg ≤ 50 ms/key). A
634
+ typed PO number or count followed by Enter stays in its field, even when
635
+ its digits happen to form a valid barcode.
636
+ - **An open form can take the scan first.** A field with
637
+ [`x-lt-scan`](hitl/x-lt-scan.md) that accepts the scan's scheme is filled
638
+ instead; every other scan, and every badge, runs globally.
386
639
 
387
640
  - On a match, the terminator is swallowed, the code's characters are
388
641
  stripped back out of whatever editable held focus (byte-exact, at the
@@ -487,3 +740,20 @@ of each machine:
487
740
  Each rule ends on a broad `show-detail` and the "no twin found" fallback.
488
741
  Walk it hardware-free: run the twin farm, open the scan panel, and paste
489
742
  `10:1:<a-printing-serial>`.
743
+
744
+ ## The bag and bin demo
745
+
746
+ `examples/seed-scan-bins.ts` puts the bench motion on the
747
+ [rollup-bin](../examples/workflows/rollup-bin/) example. Start a `rollupBin`
748
+ with a `binKey` and a few `rollupMember` bags carrying the same `binKey`,
749
+ then at the scan station:
750
+
751
+ | Scan | Code | What happens |
752
+ |---|---|---|
753
+ | Bag | `12:0:<orderId>` | The station holds the bag and shows its bin, large |
754
+ | Badge | `11:0:<badge>` | Primes the person (before the bag works too) |
755
+ | Bin | `13:0:<binKey>` | The bag joins the bin and its own row completes; the station shows "Drop it in **bin-7**. All good." until the next scan |
756
+
757
+ Scan a different bin's label instead and the station refuses with the bag's
758
+ bin named, and nothing is written. Scan the bin before the badge and the
759
+ station asks for the badge, then places the bag on its own.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.22.1",
3
+ "version": "0.22.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",