@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.
- package/README.md +2 -4
- package/build/api/escalations/analytics.d.ts +6 -0
- package/build/api/escalations/analytics.js +123 -0
- package/build/api/escalations/index.d.ts +1 -0
- package/build/api/escalations/index.js +4 -1
- package/build/api/escalations/metadata.js +1 -1
- package/build/api/escalations/resolve.js +4 -4
- package/build/api/roles.d.ts +4 -0
- package/build/api/roles.js +8 -0
- package/build/api/scan-codes/choice.d.ts +11 -0
- package/build/api/scan-codes/choice.js +136 -0
- package/build/api/scan-codes/context.d.ts +24 -0
- package/build/api/scan-codes/context.js +86 -0
- package/build/api/scan-codes/execute.d.ts +1 -0
- package/build/api/scan-codes/execute.js +49 -244
- package/build/api/scan-codes/identity.d.ts +18 -0
- package/build/api/scan-codes/identity.js +116 -0
- package/build/api/scan-codes/index.d.ts +2 -0
- package/build/api/scan-codes/index.js +6 -1
- package/build/api/scan-codes/locate.d.ts +14 -0
- package/build/api/scan-codes/locate.js +127 -0
- package/build/api/scan-codes/present.d.ts +9 -0
- package/build/api/scan-codes/present.js +59 -0
- package/build/api/scan-codes/verbs.d.ts +15 -0
- package/build/api/scan-codes/verbs.js +205 -0
- package/build/bin/ltc.js +33 -0
- package/build/lib/cli/commands/escalations.d.ts +31 -0
- package/build/lib/cli/commands/escalations.js +88 -0
- package/build/lib/db/schemas/028_role_entity_facet.sql +18 -0
- package/build/lib/db/schemas/029_scan_scheme_kind.sql +31 -0
- package/build/modules/config.d.ts +5 -0
- package/build/modules/config.js +12 -0
- package/build/routes/escalations/acting.d.ts +12 -0
- package/build/routes/escalations/acting.js +25 -0
- package/build/routes/escalations/analytics.d.ts +8 -0
- package/build/routes/escalations/analytics.js +63 -0
- package/build/routes/escalations/index.js +3 -0
- package/build/routes/escalations/resolve.js +8 -1
- package/build/routes/escalations/single.js +13 -4
- package/build/routes/roles.js +1 -1
- package/build/routes/scan-codes.js +28 -2
- package/build/scripts/ortho-populate.js +14 -0
- package/build/scripts/printer-cycle.d.ts +25 -0
- package/build/scripts/printer-cycle.js +169 -0
- package/build/scripts/twin-farm.js +19 -5
- package/build/sdk/index.d.ts +2 -0
- package/build/sdk/index.js +3 -0
- package/build/services/escalation/aggregate-sql.d.ts +36 -0
- package/build/services/escalation/aggregate-sql.js +253 -0
- package/build/services/escalation/aggregate-validate.d.ts +57 -0
- package/build/services/escalation/aggregate-validate.js +218 -0
- package/build/services/escalation/aggregates.d.ts +26 -0
- package/build/services/escalation/aggregates.js +204 -0
- package/build/services/escalation/client.js +57 -21
- package/build/services/escalation/facet-sql.js +17 -0
- package/build/services/escalation/index.d.ts +2 -0
- package/build/services/escalation/index.js +8 -1
- package/build/services/escalation/metrics-cache.d.ts +18 -0
- package/build/services/escalation/metrics-cache.js +47 -1
- package/build/services/escalation/queries.js +2 -0
- package/build/services/escalation/resolver-validation.d.ts +1 -1
- package/build/services/escalation/resolver-validation.js +12 -5
- package/build/services/escalation/submit-guard.d.ts +29 -0
- package/build/services/escalation/submit-guard.js +57 -0
- package/build/services/iam/acting-identity.d.ts +16 -0
- package/build/services/iam/acting-identity.js +60 -0
- package/build/services/role/index.js +2 -0
- package/build/services/role/sql.d.ts +10 -2
- package/build/services/role/sql.js +14 -1
- package/build/services/role/types.d.ts +16 -0
- package/build/services/scan-code/read.js +4 -0
- package/build/services/scan-code/sql.d.ts +4 -4
- package/build/services/scan-code/sql.js +14 -8
- package/build/services/scan-code/validate.d.ts +2 -0
- package/build/services/scan-code/validate.js +86 -0
- package/build/services/scan-code/write.d.ts +4 -0
- package/build/services/scan-code/write.js +16 -2
- package/build/services/user/crud.d.ts +6 -0
- package/build/services/user/crud.js +16 -0
- package/build/services/user/index.d.ts +1 -1
- package/build/services/user/index.js +2 -1
- package/build/services/user/sql.d.ts +8 -0
- package/build/services/user/sql.js +9 -1
- package/build/shared/form-validation/index.d.ts +4 -0
- package/build/shared/form-validation/index.js +4 -0
- package/build/shared/form-validation/x-lt-labels.d.ts +28 -0
- package/build/shared/form-validation/x-lt-labels.js +42 -0
- package/build/shared/form-validation/x-lt-query.d.ts +56 -0
- package/build/shared/form-validation/x-lt-query.js +49 -0
- package/build/shared/form-validation/x-lt-submit-guard.d.ts +46 -0
- package/build/shared/form-validation/x-lt-submit-guard.js +25 -0
- package/build/shared/form-validation/x-lt-submit-on-claim.d.ts +22 -0
- package/build/shared/form-validation/x-lt-submit-on-claim.js +28 -0
- package/build/system/mcp-servers/admin/escalations.js +34 -0
- package/build/system/mcp-servers/admin/scan-codes.js +17 -1
- package/build/system/mcp-servers/admin/schemas.d.ts +643 -25
- package/build/system/mcp-servers/admin/schemas.js +127 -19
- package/build/system/mcp-servers/admin/users.js +3 -1
- package/build/system/mcp-servers/human-queue-schemas.d.ts +2 -2
- package/build/system/seed/tool-manifests-admin.d.ts +2283 -0
- package/build/system/seed/tool-manifests-admin.js +8 -5
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/analytics.d.ts +154 -0
- package/build/types/analytics.js +14 -0
- package/build/types/facets.d.ts +13 -0
- package/build/types/index.d.ts +5 -2
- package/build/types/index.js +5 -1
- package/build/types/scan-choice.d.ts +49 -0
- package/build/types/scan-choice.js +9 -0
- package/build/types/scan-code.d.ts +72 -1
- package/build/types/scan-code.js +30 -1
- package/dashboard/dist/assets/{AdminDashboard-Bv3pHBzn.js → AdminDashboard-DJ3LJQ09.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-Bv3pHBzn.js.map → AdminDashboard-DJ3LJQ09.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-Dk2zlvDb.js → AgentConfigPage-BMMvaD0z.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-Dk2zlvDb.js.map → AgentConfigPage-BMMvaD0z.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-DUno6EGs.js → AgentDetailPage-C6wfsnRe.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-DUno6EGs.js.map → AgentDetailPage-C6wfsnRe.js.map} +1 -1
- package/dashboard/dist/assets/AgentsPage-Cwqka7nE.js +2 -0
- package/dashboard/dist/assets/{AgentsPage-CToVu1qj.js.map → AgentsPage-Cwqka7nE.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-CVlG_AVC.js +2 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-CVlG_AVC.js.map +1 -0
- package/dashboard/dist/assets/{BotPicker-DQOpVB4M.js → BotPicker-BBkJBXZB.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-DQOpVB4M.js.map → BotPicker-BBkJBXZB.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-Dmf46PUJ.js → CapabilitiesPage-wxmE4917.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-Dmf46PUJ.js.map → CapabilitiesPage-wxmE4917.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-CDADefUY.js → CollapsibleSection-B3FsrHwH.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-CDADefUY.js.map → CollapsibleSection-B3FsrHwH.js.map} +1 -1
- package/dashboard/dist/assets/CountdownTimer-B3nCqJdC.js +2 -0
- package/dashboard/dist/assets/CountdownTimer-B3nCqJdC.js.map +1 -0
- package/dashboard/dist/assets/{CredentialsPage-8LIEvomi.js → CredentialsPage-Bv875IHe.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-8LIEvomi.js.map → CredentialsPage-Bv875IHe.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-CK3WSsM5.js → CronLabel-CSzjWuwj.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-CK3WSsM5.js.map → CronLabel-CSzjWuwj.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-FPmlA6cE.js → CustomDurationPicker-CCpqf3e4.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-FPmlA6cE.js.map → CustomDurationPicker-CCpqf3e4.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-CL9gA7pu.js → DropZone-CJkk-U3o.js} +2 -2
- package/dashboard/dist/assets/{DropZone-CL9gA7pu.js.map → DropZone-CJkk-U3o.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-BZVkVxE0.js → ElapsedCell-WV6pUaKo.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-BZVkVxE0.js.map → ElapsedCell-WV6pUaKo.js.map} +1 -1
- package/dashboard/dist/assets/EntityTimelinePanel-DrL86Yrf.js +2 -0
- package/dashboard/dist/assets/EntityTimelinePanel-DrL86Yrf.js.map +1 -0
- package/dashboard/dist/assets/{EscalationListSchemaPage-kYLdUtZX.js → EscalationListSchemaPage-CTQCALw4.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-kYLdUtZX.js.map → EscalationListSchemaPage-CTQCALw4.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-kUmCDivo.js → EscalationSchemaPage-D_UgP0pv.js} +3 -3
- package/dashboard/dist/assets/{EscalationSchemaPage-kUmCDivo.js.map → EscalationSchemaPage-D_UgP0pv.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-D17auO-m.js → EscalationsOverview-DuIUyxr0.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-D17auO-m.js.map → EscalationsOverview-DuIUyxr0.js.map} +1 -1
- package/dashboard/dist/assets/EventTable-BV6unphX.js +2 -0
- package/dashboard/dist/assets/EventTable-BV6unphX.js.map +1 -0
- package/dashboard/dist/assets/FilterBar-DYFgzXNB.js +2 -0
- package/dashboard/dist/assets/FilterBar-DYFgzXNB.js.map +1 -0
- package/dashboard/dist/assets/{GraphInvokePage-D11uOnZi.js → GraphInvokePage-BGHVUipJ.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-D11uOnZi.js.map → GraphInvokePage-BGHVUipJ.js.map} +1 -1
- package/dashboard/dist/assets/HomePage-B-S2cK3E.js +2 -0
- package/dashboard/dist/assets/{HomePage-Bcq10Bxx.js.map → HomePage-B-S2cK3E.js.map} +1 -1
- package/dashboard/dist/assets/ListToolbar-Bl7F3A28.js +2 -0
- package/dashboard/dist/assets/{ListToolbar-IPXsTb3Q.js.map → ListToolbar-Bl7F3A28.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-Dx2j0KkF.js → McpOverview-_T9IsCD_.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-Dx2j0KkF.js.map → McpOverview-_T9IsCD_.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-CzF_bna8.js → McpQueryDetailPage-DtsVqKPw.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-CzF_bna8.js.map → McpQueryDetailPage-DtsVqKPw.js.map} +1 -1
- package/dashboard/dist/assets/McpQueryPage-DX5u-1bx.js +2 -0
- package/dashboard/dist/assets/{McpQueryPage-CNsuiw_q.js.map → McpQueryPage-DX5u-1bx.js.map} +1 -1
- package/dashboard/dist/assets/McpRunDetailPage-BhcqqtYS.js +2 -0
- package/dashboard/dist/assets/McpRunDetailPage-BhcqqtYS.js.map +1 -0
- package/dashboard/dist/assets/{McpRunsPage-CK96s3Nt.js → McpRunsPage-L-1y8wnX.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-CK96s3Nt.js.map → McpRunsPage-L-1y8wnX.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-B9Vc_Pz2.js → NamespacePill-in1SLd0W.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-B9Vc_Pz2.js.map → NamespacePill-in1SLd0W.js.map} +1 -1
- package/dashboard/dist/assets/OperationsPage-i8KcDpKo.js +3 -0
- package/dashboard/dist/assets/OperationsPage-i8KcDpKo.js.map +1 -0
- package/dashboard/dist/assets/OperatorDashboard-CIbDzOlh.js +2 -0
- package/dashboard/dist/assets/{OperatorDashboard-C-jE0IRA.js.map → OperatorDashboard-CIbDzOlh.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-Ce4GcIYo.js → PageHeader-BJgWMZtw.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-Ce4GcIYo.js.map → PageHeader-BJgWMZtw.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-Ck0KZvXD.js → PageHeaderWithStats-Ccio4vyG.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-Ck0KZvXD.js.map → PageHeaderWithStats-Ccio4vyG.js.map} +1 -1
- package/dashboard/dist/assets/{PersonaDetailPage-CJ6jqo7h.js → PersonaDetailPage-BOaf0TW1.js} +2 -2
- package/dashboard/dist/assets/{PersonaDetailPage-CJ6jqo7h.js.map → PersonaDetailPage-BOaf0TW1.js.map} +1 -1
- package/dashboard/dist/assets/{PersonasPage-D8hL3kL5.js → PersonasPage-DSdRb90f.js} +2 -2
- package/dashboard/dist/assets/{PersonasPage-D8hL3kL5.js.map → PersonasPage-DSdRb90f.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-Vnx8Z5_y.js → ProcessDetailPage-DN9NqfeP.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-Vnx8Z5_y.js.map → ProcessDetailPage-DN9NqfeP.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-DRYtvS3N.js → ProcessesListPage-CfQEWaD4.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-DRYtvS3N.js.map → ProcessesListPage-CfQEWaD4.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-BC5HL7ls.js +8 -0
- package/dashboard/dist/assets/RoleDetailPage-BC5HL7ls.js.map +1 -0
- package/dashboard/dist/assets/{RolePill-CnuBPhPw.js → RolePill-BNAP-uK6.js} +2 -2
- package/dashboard/dist/assets/{RolePill-CnuBPhPw.js.map → RolePill-BNAP-uK6.js.map} +1 -1
- package/dashboard/dist/assets/RolesPage-DCnW0VmD.js +2 -0
- package/dashboard/dist/assets/RolesPage-DCnW0VmD.js.map +1 -0
- package/dashboard/dist/assets/RunAsSelector-twbuX7fB.js +2 -0
- package/dashboard/dist/assets/{RunAsSelector-CS3KQ-BF.js.map → RunAsSelector-twbuX7fB.js.map} +1 -1
- package/dashboard/dist/assets/ScanCodesPage-CPNg6K--.js +2 -0
- package/dashboard/dist/assets/ScanCodesPage-CPNg6K--.js.map +1 -0
- package/dashboard/dist/assets/ScanSchemeDetailPage-DyLmCamO.js +2 -0
- package/dashboard/dist/assets/ScanSchemeDetailPage-DyLmCamO.js.map +1 -0
- package/dashboard/dist/assets/ScanStationPage-DoZccU5B.js +2 -0
- package/dashboard/dist/assets/ScanStationPage-DoZccU5B.js.map +1 -0
- package/dashboard/dist/assets/{ScopeBadge-BUpYpS3x.js → ScopeBadge-BG7KkZ8Q.js} +2 -2
- package/dashboard/dist/assets/{ScopeBadge-BUpYpS3x.js.map → ScopeBadge-BG7KkZ8Q.js.map} +1 -1
- package/dashboard/dist/assets/SegmentedTabs-DXNzY05q.js +2 -0
- package/dashboard/dist/assets/SegmentedTabs-DXNzY05q.js.map +1 -0
- package/dashboard/dist/assets/StickyPagination-DIeYMPPd.js +2 -0
- package/dashboard/dist/assets/StickyPagination-DIeYMPPd.js.map +1 -0
- package/dashboard/dist/assets/{StreamMessageDetail-DyJyZwBl.js → StreamMessageDetail-gU4CUeQI.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-DyJyZwBl.js.map → StreamMessageDetail-gU4CUeQI.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-BlzV74V5.js → SwimlaneTimeline-Br-nBA4l.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-BlzV74V5.js.map → SwimlaneTimeline-Br-nBA4l.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-BbZ25nNe.js → TagInput-CvM5n6ww.js} +2 -2
- package/dashboard/dist/assets/{TagInput-BbZ25nNe.js.map → TagInput-CvM5n6ww.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-B0p_Z4J_.js → TaskDetailPage-_9cr_R0u.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-B0p_Z4J_.js.map → TaskDetailPage-_9cr_R0u.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-D0QKBrN3.js → TaskQueuePill-D4JPHXOq.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-D0QKBrN3.js.map → TaskQueuePill-D4JPHXOq.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-CiGR2QiQ.js → TasksListPage-DVENC9Jp.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-CiGR2QiQ.js.map → TasksListPage-DVENC9Jp.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-D_DA9ucC.js → TimeAgo-C9yYnOui.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-D_DA9ucC.js.map → TimeAgo-C9yYnOui.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-C7rkC9nh.js → TimestampCell-DoB8cYgw.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-C7rkC9nh.js.map → TimestampCell-DoB8cYgw.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-8xY9rkoc.js → ToolPill-BUjbC-Tj.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-8xY9rkoc.js.map → ToolPill-BUjbC-Tj.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-E8efYGYs.js → ToolTestPanel-CBtsIP8K.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-E8efYGYs.js.map → ToolTestPanel-CBtsIP8K.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-B455XrRc.js → TopicDetailPage-mR4Jaz83.js} +4 -4
- package/dashboard/dist/assets/{TopicDetailPage-B455XrRc.js.map → TopicDetailPage-mR4Jaz83.js.map} +1 -1
- package/dashboard/dist/assets/TopicsPage-CP7HFxIC.js +2 -0
- package/dashboard/dist/assets/{TopicsPage-1VAc29Qw.js.map → TopicsPage-CP7HFxIC.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-DikzjQHJ.js → UserName-BnLF6yhz.js} +2 -2
- package/dashboard/dist/assets/{UserName-DikzjQHJ.js.map → UserName-BnLF6yhz.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-xL956RZi.js +2 -0
- package/dashboard/dist/assets/WorkflowExecutionPage-xL956RZi.js.map +1 -0
- package/dashboard/dist/assets/{WorkflowPill-Da406nEj.js → WorkflowPill-tXU0-eXu.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-Da406nEj.js.map → WorkflowPill-tXU0-eXu.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowsDashboard-B-u7zFKM.js +2 -0
- package/dashboard/dist/assets/{WorkflowsDashboard-BMQd_0tW.js.map → WorkflowsDashboard-B-u7zFKM.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-CdxjVifp.js → WorkflowsOverview-CqZVarK-.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-CdxjVifp.js.map → WorkflowsOverview-CqZVarK-.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-DNVffIXa.js → YamlWorkflowDetailPage-DiFLavW0.js} +10 -10
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-DNVffIXa.js.map → YamlWorkflowDetailPage-DiFLavW0.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-BGP8wuos.js → YamlWorkflowsPage-BuQM6uZA.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-BGP8wuos.js.map → YamlWorkflowsPage-BuQM6uZA.js.map} +1 -1
- package/dashboard/dist/assets/{agents-BZJLIJ4h.js → agents-DU9GPovp.js} +2 -2
- package/dashboard/dist/assets/{agents-BZJLIJ4h.js.map → agents-DU9GPovp.js.map} +1 -1
- package/dashboard/dist/assets/{bots-ClLcDGJb.js → bots-Cy-Hmg7w.js} +2 -2
- package/dashboard/dist/assets/{bots-ClLcDGJb.js.map → bots-Cy-Hmg7w.js.map} +1 -1
- package/dashboard/dist/assets/capabilities-sSJJqrp-.js +2 -0
- package/dashboard/dist/assets/{capabilities-IaCcS7ge.js.map → capabilities-sSJJqrp-.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-Zi4m_MSx.js → controlplane-TVCjDHQ-.js} +2 -2
- package/dashboard/dist/assets/{controlplane-Zi4m_MSx.js.map → controlplane-TVCjDHQ-.js.map} +1 -1
- package/dashboard/dist/assets/escalation-columns-BpkjKYGd.js +2 -0
- package/dashboard/dist/assets/escalation-columns-BpkjKYGd.js.map +1 -0
- package/dashboard/dist/assets/index-5TblaCXQ.css +1 -0
- package/dashboard/dist/assets/index-7BP6FaCZ.js +2 -0
- package/dashboard/dist/assets/{index-Dldi3Q3M.js.map → index-7BP6FaCZ.js.map} +1 -1
- package/dashboard/dist/assets/{index-BzkE48xJ.js → index-B7IaBPBg.js} +4 -4
- package/dashboard/dist/assets/{index-BzkE48xJ.js.map → index-B7IaBPBg.js.map} +1 -1
- package/dashboard/dist/assets/index-BE0phFw0.js +2 -0
- package/dashboard/dist/assets/{index-CLmZaNTK.js.map → index-BE0phFw0.js.map} +1 -1
- package/dashboard/dist/assets/{index-BqGnhkh0.js → index-BYNN9N1T.js} +2 -2
- package/dashboard/dist/assets/{index-BqGnhkh0.js.map → index-BYNN9N1T.js.map} +1 -1
- package/dashboard/dist/assets/{index-kiKZresb.js → index-CLPVqbVY.js} +2 -2
- package/dashboard/dist/assets/{index-kiKZresb.js.map → index-CLPVqbVY.js.map} +1 -1
- package/dashboard/dist/assets/{index-C3o4OisO.js → index-Cfv86_L9.js} +2 -2
- package/dashboard/dist/assets/{index-C3o4OisO.js.map → index-Cfv86_L9.js.map} +1 -1
- package/dashboard/dist/assets/{index-yGNt29V9.js → index-CtwbQ_-m.js} +2 -2
- package/dashboard/dist/assets/{index-yGNt29V9.js.map → index-CtwbQ_-m.js.map} +1 -1
- package/dashboard/dist/assets/index-DOai950R.js +3 -0
- package/dashboard/dist/assets/index-DOai950R.js.map +1 -0
- package/dashboard/dist/assets/index-DTtHNC-D.js +5 -0
- package/dashboard/dist/assets/{index-CvNBdkB1.js.map → index-DTtHNC-D.js.map} +1 -1
- package/dashboard/dist/assets/{index-CJu2PBNx.js → index-DsWnVyVN.js} +2 -2
- package/dashboard/dist/assets/{index-CJu2PBNx.js.map → index-DsWnVyVN.js.map} +1 -1
- package/dashboard/dist/assets/{index-D0zFReGL.js → index-Dy-MMYti.js} +2 -2
- package/dashboard/dist/assets/{index-D0zFReGL.js.map → index-Dy-MMYti.js.map} +1 -1
- package/dashboard/dist/assets/index-errgQT5x.js +65 -0
- package/dashboard/dist/assets/index-errgQT5x.js.map +1 -0
- package/dashboard/dist/assets/{index-CmnNqpWg.js → index-fgBDhnI6.js} +2 -2
- package/dashboard/dist/assets/{index-CmnNqpWg.js.map → index-fgBDhnI6.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-DWLxGTib.js → knowledge-arh4NfmL.js} +2 -2
- package/dashboard/dist/assets/{knowledge-DWLxGTib.js.map → knowledge-arh4NfmL.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-B6AARZ2I.js → mcp-QGf5jHCo.js} +2 -2
- package/dashboard/dist/assets/{mcp-B6AARZ2I.js.map → mcp-QGf5jHCo.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-DrkvBU3a.js → mcp-query-BEJN1nMZ.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-DrkvBU3a.js.map → mcp-query-BEJN1nMZ.js.map} +1 -1
- package/dashboard/dist/assets/{personas-Cmz2SAEU.js → personas-DYtw1COf.js} +2 -2
- package/dashboard/dist/assets/{personas-Cmz2SAEU.js.map → personas-DYtw1COf.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-CSgfVCMy.js → pipelines-Byi6PnbE.js} +2 -2
- package/dashboard/dist/assets/{pipelines-CSgfVCMy.js.map → pipelines-Byi6PnbE.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-UOtUkDk-.js → tasks-D0SQdENz.js} +2 -2
- package/dashboard/dist/assets/{tasks-UOtUkDk-.js.map → tasks-D0SQdENz.js.map} +1 -1
- package/dashboard/dist/assets/{topics-Df5hGh6o.js → topics-DgDv5ZXP.js} +2 -2
- package/dashboard/dist/assets/{topics-Df5hGh6o.js.map → topics-DgDv5ZXP.js.map} +1 -1
- package/dashboard/dist/assets/useEventHooks-DZaYzE7s.js +2 -0
- package/dashboard/dist/assets/{useEventHooks-PDbuSJ12.js.map → useEventHooks-DZaYzE7s.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-tZSOlge3.js → useNamespace-C1OqiNXN.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-tZSOlge3.js.map → useNamespace-C1OqiNXN.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-CINLzE32.js → useYamlActivityEvents-CkmXDm-Z.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-CINLzE32.js.map → useYamlActivityEvents-CkmXDm-Z.js.map} +1 -1
- package/dashboard/dist/assets/{users-G8qwyZKS.js → users-D4DCFPH1.js} +2 -2
- package/dashboard/dist/assets/{users-G8qwyZKS.js.map → users-D4DCFPH1.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-BpZiWH2f.js → vendor-icons-BxuyFxH2.js} +156 -136
- package/dashboard/dist/assets/vendor-icons-BxuyFxH2.js.map +1 -0
- package/dashboard/dist/assets/{workflows-DbPqi1TW.js → workflows-qyc0ku_5.js} +2 -2
- package/dashboard/dist/assets/{workflows-DbPqi1TW.js.map → workflows-qyc0ku_5.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-DozD8hpR.js → yaml-workflows-DWFww9Xn.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-DozD8hpR.js.map → yaml-workflows-DWFww9Xn.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/api/http/escalations.md +133 -0
- package/docs/api/http/roles.md +6 -0
- package/docs/api/mcp/admin.md +53 -0
- package/docs/api/sdk/escalations.md +58 -0
- package/docs/cli.md +11 -0
- package/docs/dashboard.md +22 -20
- package/docs/data.md +12 -4
- package/docs/design-principles.md +9 -7
- package/docs/escalation-analytics.md +251 -0
- package/docs/hitl/roles.md +1 -1
- package/docs/hitl/x-lt-embed.md +8 -4
- package/docs/hitl/x-lt-footer.md +67 -0
- package/docs/hitl/x-lt-list-schema.md +10 -3
- package/docs/hitl-guide.md +11 -2
- package/docs/iam.md +12 -0
- package/docs/operations.md +17 -0
- package/docs/scan-codes.md +112 -0
- package/docs/sdk.md +1 -1
- package/package.json +2 -1
- package/dashboard/dist/assets/AgentsPage-CToVu1qj.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-DldboL4W.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-DldboL4W.js.map +0 -1
- package/dashboard/dist/assets/CountdownTimer-DPjIkerB.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-DPjIkerB.js.map +0 -1
- package/dashboard/dist/assets/EventTable-CwPWl163.js +0 -2
- package/dashboard/dist/assets/EventTable-CwPWl163.js.map +0 -1
- package/dashboard/dist/assets/FilterBar-D7GghUsU.js +0 -2
- package/dashboard/dist/assets/FilterBar-D7GghUsU.js.map +0 -1
- package/dashboard/dist/assets/HomePage-Bcq10Bxx.js +0 -2
- package/dashboard/dist/assets/ListToolbar-IPXsTb3Q.js +0 -2
- package/dashboard/dist/assets/McpQueryPage-CNsuiw_q.js +0 -2
- package/dashboard/dist/assets/McpRunDetailPage-BJguEMS0.js +0 -2
- package/dashboard/dist/assets/McpRunDetailPage-BJguEMS0.js.map +0 -1
- package/dashboard/dist/assets/OperationsPage-DvWrLuH6.js +0 -2
- package/dashboard/dist/assets/OperationsPage-DvWrLuH6.js.map +0 -1
- package/dashboard/dist/assets/OperatorDashboard-C-jE0IRA.js +0 -2
- package/dashboard/dist/assets/RoleDetailPage-BKkUvTO-.js +0 -8
- package/dashboard/dist/assets/RoleDetailPage-BKkUvTO-.js.map +0 -1
- package/dashboard/dist/assets/RolesPage-BHzWK9n6.js +0 -2
- package/dashboard/dist/assets/RolesPage-BHzWK9n6.js.map +0 -1
- package/dashboard/dist/assets/RunAsSelector-CS3KQ-BF.js +0 -2
- package/dashboard/dist/assets/ScanCodesPage-BW4P4zWf.js +0 -2
- package/dashboard/dist/assets/ScanCodesPage-BW4P4zWf.js.map +0 -1
- package/dashboard/dist/assets/ScanSchemeDetailPage-BLykA4j-.js +0 -2
- package/dashboard/dist/assets/ScanSchemeDetailPage-BLykA4j-.js.map +0 -1
- package/dashboard/dist/assets/StickyPagination-hMrnLXde.js +0 -2
- package/dashboard/dist/assets/StickyPagination-hMrnLXde.js.map +0 -1
- package/dashboard/dist/assets/TopicsPage-1VAc29Qw.js +0 -2
- package/dashboard/dist/assets/WorkflowExecutionPage-C15GSAoe.js +0 -2
- package/dashboard/dist/assets/WorkflowExecutionPage-C15GSAoe.js.map +0 -1
- package/dashboard/dist/assets/WorkflowsDashboard-BMQd_0tW.js +0 -2
- package/dashboard/dist/assets/capabilities-IaCcS7ge.js +0 -2
- package/dashboard/dist/assets/escalation-columns-BY2mg9DY.js +0 -2
- package/dashboard/dist/assets/escalation-columns-BY2mg9DY.js.map +0 -1
- package/dashboard/dist/assets/index-CLmZaNTK.js +0 -2
- package/dashboard/dist/assets/index-CfAi1OsI.js +0 -65
- package/dashboard/dist/assets/index-CfAi1OsI.js.map +0 -1
- package/dashboard/dist/assets/index-CvNBdkB1.js +0 -5
- package/dashboard/dist/assets/index-Dldi3Q3M.js +0 -2
- package/dashboard/dist/assets/index-mKcQGi2B.js +0 -3
- package/dashboard/dist/assets/index-mKcQGi2B.js.map +0 -1
- package/dashboard/dist/assets/index-zQiUPfxA.css +0 -1
- package/dashboard/dist/assets/useEventHooks-PDbuSJ12.js +0 -2
- 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`.
|
package/docs/hitl/roles.md
CHANGED
|
@@ -57,7 +57,7 @@ const decision = await conditionLT<{ confirmed: boolean; address: string }>(sign
|
|
|
57
57
|
|
|
58
58
|
## Escalation Chains
|
|
59
59
|
|
|
60
|
-
|
|
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
|
package/docs/hitl/x-lt-embed.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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",
|
|
35
|
-
"label": "Service",
|
|
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
|
package/docs/hitl-guide.md
CHANGED
|
@@ -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** —
|
|
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,
|
|
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:
|
package/docs/operations.md
CHANGED
|
@@ -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.
|
package/docs/scan-codes.md
CHANGED
|
@@ -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.
|
|
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",
|