@hotmeshio/long-tail 0.22.0 → 0.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/escalations/accumulate.d.ts +10 -1
- package/build/api/escalations/accumulate.js +121 -34
- package/build/api/escalations/metadata.d.ts +5 -0
- package/build/api/escalations/metadata.js +6 -2
- package/build/api/escalations/resolve-batch.js +18 -11
- package/build/api/roles.js +4 -0
- package/build/api/scan-codes/badge-policy.d.ts +10 -0
- package/build/api/scan-codes/badge-policy.js +72 -0
- package/build/api/scan-codes/choice.js +18 -11
- package/build/api/scan-codes/context.d.ts +45 -1
- package/build/api/scan-codes/context.js +51 -3
- package/build/api/scan-codes/execute.js +73 -35
- package/build/api/scan-codes/grant.d.ts +27 -0
- package/build/api/scan-codes/grant.js +67 -0
- package/build/api/scan-codes/hold.d.ts +10 -0
- package/build/api/scan-codes/hold.js +98 -0
- package/build/api/scan-codes/identity.d.ts +5 -2
- package/build/api/scan-codes/identity.js +26 -8
- package/build/api/scan-codes/subject.d.ts +15 -0
- package/build/api/scan-codes/subject.js +68 -0
- package/build/api/scan-codes/verb-accumulate.d.ts +24 -0
- package/build/api/scan-codes/verb-accumulate.js +147 -0
- package/build/api/scan-codes/verb-fill.d.ts +6 -0
- package/build/api/scan-codes/verb-fill.js +152 -0
- package/build/api/scan-codes/verbs-subject.d.ts +14 -0
- package/build/api/scan-codes/verbs-subject.js +270 -0
- package/build/api/scan-codes/verbs.d.ts +7 -22
- package/build/api/scan-codes/verbs.js +35 -114
- package/build/api/workflows/invocation.d.ts +8 -4
- package/build/api/workflows/invocation.js +26 -9
- package/build/index.d.ts +1 -0
- package/build/index.js +8 -2
- package/build/lib/cli/commands/workflows.js +9 -4
- package/build/lib/db/schemas/037_scan_gtin_and_subject_grants.sql +27 -0
- package/build/routes/escalations/accumulate.js +13 -6
- package/build/routes/escalations/acting.d.ts +5 -0
- package/build/routes/escalations/acting.js +8 -1
- package/build/routes/scan-codes.js +3 -0
- package/build/sdk/index.d.ts +3 -0
- package/build/services/escalation/crud.d.ts +2 -2
- package/build/services/escalation/crud.js +4 -2
- package/build/services/escalation/facet-sql.js +8 -0
- package/build/services/escalation/sql.d.ts +4 -2
- package/build/services/escalation/sql.js +10 -2
- package/build/services/export/index.d.ts +8 -3
- package/build/services/export/index.js +15 -21
- package/build/services/iam/acting-identity.d.ts +29 -4
- package/build/services/iam/acting-identity.js +60 -17
- package/build/services/iam/ephemeral.d.ts +21 -0
- package/build/services/iam/ephemeral.js +32 -1
- package/build/services/iam/sql.d.ts +10 -0
- package/build/services/iam/sql.js +32 -3
- package/build/services/pipelines/events.js +2 -1
- package/build/services/pipelines/execution-builder.js +2 -1
- package/build/services/pipelines/queries.js +2 -1
- package/build/services/role/badge-grant.d.ts +5 -0
- package/build/services/role/badge-grant.js +36 -0
- package/build/services/role/seed.js +5 -0
- package/build/services/role/types.d.ts +14 -0
- package/build/services/role/types.js +5 -0
- package/build/services/scan-code/index.d.ts +3 -2
- package/build/services/scan-code/index.js +7 -1
- package/build/services/scan-code/parse.d.ts +23 -20
- package/build/services/scan-code/parse.js +45 -90
- package/build/services/scan-code/read.js +1 -0
- package/build/services/scan-code/sql.d.ts +3 -3
- package/build/services/scan-code/sql.js +11 -8
- package/build/services/scan-code/validate-subject.d.ts +7 -0
- package/build/services/scan-code/validate-subject.js +158 -0
- package/build/services/scan-code/validate.d.ts +7 -0
- package/build/services/scan-code/validate.js +111 -16
- package/build/services/scan-code/write.d.ts +1 -0
- package/build/services/scan-code/write.js +9 -3
- package/build/services/workflow-state.d.ts +21 -0
- package/build/services/workflow-state.js +39 -0
- package/build/shared/form-validation/field-validator.js +4 -0
- package/build/shared/form-validation/index.d.ts +1 -0
- package/build/shared/form-validation/index.js +1 -0
- package/build/shared/form-validation/x-lt-scan.d.ts +52 -0
- package/build/shared/form-validation/x-lt-scan.js +166 -0
- package/build/shared/scan-code/gtin.d.ts +5 -0
- package/build/shared/scan-code/gtin.js +25 -0
- package/build/shared/scan-code/index.d.ts +2 -0
- package/build/shared/scan-code/index.js +10 -0
- package/build/shared/scan-code/parse.d.ts +46 -0
- package/build/shared/scan-code/parse.js +133 -0
- package/build/shared/workflow-state.d.ts +17 -0
- package/build/shared/workflow-state.js +29 -0
- package/build/system/mcp-servers/admin/escalations.js +8 -4
- package/build/system/mcp-servers/admin/scan-codes.js +10 -4
- package/build/system/mcp-servers/admin/schemas.d.ts +792 -23
- package/build/system/mcp-servers/admin/schemas.js +61 -4
- package/build/system/mcp-servers/admin/workflows.js +20 -31
- package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -6
- package/build/system/seed/tool-manifests-admin.d.ts +94 -0
- package/build/system/seed/tool-manifests-admin.js +3 -3
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/escalation.d.ts +2 -0
- package/build/types/escalation.js +3 -1
- package/build/types/facets.d.ts +14 -0
- package/build/types/index.d.ts +3 -3
- package/build/types/index.js +8 -1
- package/build/types/scan-code/constants.d.ts +182 -0
- package/build/types/{scan-code.js → scan-code/constants.js} +62 -12
- package/build/types/scan-code/index.d.ts +3 -0
- package/build/types/scan-code/index.js +17 -0
- package/build/types/scan-code/response.d.ts +140 -0
- package/build/types/scan-code/response.js +2 -0
- package/build/types/scan-code/rule.d.ts +199 -0
- package/build/types/scan-code/rule.js +2 -0
- package/build/types/startup.d.ts +3 -1
- package/dashboard/dist/assets/{AdminDashboard-Covdam48.js → AdminDashboard-DBW12qQt.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-Covdam48.js.map → AdminDashboard-DBW12qQt.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-CE1k6J5r.js → AgentConfigPage-CmHv8U9M.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-CE1k6J5r.js.map → AgentConfigPage-CmHv8U9M.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-CWJZIZP1.js → AgentDetailPage-D1scIbz-.js} +3 -3
- package/dashboard/dist/assets/{AgentDetailPage-CWJZIZP1.js.map → AgentDetailPage-D1scIbz-.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage---Dg5ufF.js → AgentsPage-BA_ai9HJ.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage---Dg5ufF.js.map → AgentsPage-BA_ai9HJ.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-C2_5aEFW.js +2 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-C2_5aEFW.js.map +1 -0
- package/dashboard/dist/assets/{BotPicker-D3drO3-T.js → BotPicker-BZo4Kp4a.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-D3drO3-T.js.map → BotPicker-BZo4Kp4a.js.map} +1 -1
- package/dashboard/dist/assets/{BulkAssignModal-BOAWsViq.js → BulkAssignModal-BFhyxY2T.js} +2 -2
- package/dashboard/dist/assets/{BulkAssignModal-BOAWsViq.js.map → BulkAssignModal-BFhyxY2T.js.map} +1 -1
- package/dashboard/dist/assets/CapabilitiesPage-CvOX6vAc.js +2 -0
- package/dashboard/dist/assets/{CapabilitiesPage-RGgD3YZO.js.map → CapabilitiesPage-CvOX6vAc.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-DNkZn87n.js → CollapsibleSection-GCJsV9Oq.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-DNkZn87n.js.map → CollapsibleSection-GCJsV9Oq.js.map} +1 -1
- package/dashboard/dist/assets/{ConfirmDeleteModal-BqzOwhPr.js → ConfirmDeleteModal-CAgYLcef.js} +2 -2
- package/dashboard/dist/assets/{ConfirmDeleteModal-BqzOwhPr.js.map → ConfirmDeleteModal-CAgYLcef.js.map} +1 -1
- package/dashboard/dist/assets/CountdownTimer-Dik2z0M_.js +2 -0
- package/dashboard/dist/assets/{CountdownTimer-BaexQMK6.js.map → CountdownTimer-Dik2z0M_.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-D7abp0aS.js → CredentialsPage-twd2xCOW.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-D7abp0aS.js.map → CredentialsPage-twd2xCOW.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-DZiWmTwJ.js → CronLabel-DdnIuudX.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-DZiWmTwJ.js.map → CronLabel-DdnIuudX.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-f1o-GLM8.js → DropZone-B1Tdfddc.js} +2 -2
- package/dashboard/dist/assets/{DropZone-f1o-GLM8.js.map → DropZone-B1Tdfddc.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-BdGO_w4v.js → ElapsedCell-DCctVv5R.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-BdGO_w4v.js.map → ElapsedCell-DCctVv5R.js.map} +1 -1
- package/dashboard/dist/assets/{EntityTimelinePanel-DIDcF5Yu.js → EntityTimelinePanel-CMnskyWv.js} +2 -2
- package/dashboard/dist/assets/{EntityTimelinePanel-DIDcF5Yu.js.map → EntityTimelinePanel-CMnskyWv.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-C28rn9XC.js → EscalationListSchemaPage-VfCn6WDb.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-C28rn9XC.js.map → EscalationListSchemaPage-VfCn6WDb.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListView-CkBnkpDJ.js → EscalationListView-CGQZxcC-.js} +2 -2
- package/dashboard/dist/assets/{EscalationListView-CkBnkpDJ.js.map → EscalationListView-CGQZxcC-.js.map} +1 -1
- package/dashboard/dist/assets/EscalationSchemaPage-BX0tx-PM.js +9 -0
- package/dashboard/dist/assets/EscalationSchemaPage-BX0tx-PM.js.map +1 -0
- package/dashboard/dist/assets/EscalationTimeline-DKoSM56J.js +2 -0
- package/dashboard/dist/assets/EscalationTimeline-DKoSM56J.js.map +1 -0
- package/dashboard/dist/assets/{EscalationTitleSelect-CzKfavri.js → EscalationTitleSelect-DWGLBOmS.js} +2 -2
- package/dashboard/dist/assets/{EscalationTitleSelect-CzKfavri.js.map → EscalationTitleSelect-DWGLBOmS.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-Tdu3Yx2c.js → EscalationsOverview-C2XoguaA.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-Tdu3Yx2c.js.map → EscalationsOverview-C2XoguaA.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-DiIxOJwO.js → EventTable-CBUWq7--.js} +2 -2
- package/dashboard/dist/assets/{EventTable-DiIxOJwO.js.map → EventTable-CBUWq7--.js.map} +1 -1
- package/dashboard/dist/assets/{FilterBar-CvMBCVIo.js → FilterBar-8IdbleZ9.js} +2 -2
- package/dashboard/dist/assets/{FilterBar-CvMBCVIo.js.map → FilterBar-8IdbleZ9.js.map} +1 -1
- package/dashboard/dist/assets/GrantScopeField-BzWGzBfA.js +2 -0
- package/dashboard/dist/assets/GrantScopeField-BzWGzBfA.js.map +1 -0
- package/dashboard/dist/assets/{GraphInvokePage-DO-yyhi6.js → GraphInvokePage-CUvoPzsW.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-DO-yyhi6.js.map → GraphInvokePage-CUvoPzsW.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-DSS3eHR2.js → HomePage-BP7v9GLJ.js} +2 -2
- package/dashboard/dist/assets/{HomePage-DSS3eHR2.js.map → HomePage-BP7v9GLJ.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-C3-Tqqub.js → ListToolbar-Cnacuwoy.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-C3-Tqqub.js.map → ListToolbar-Cnacuwoy.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-DEJmsdVi.js → McpOverview-C2JoNoDA.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-DEJmsdVi.js.map → McpOverview-C2JoNoDA.js.map} +1 -1
- package/dashboard/dist/assets/McpQueryDetailPage-C_nih0w-.js +5 -0
- package/dashboard/dist/assets/McpQueryDetailPage-C_nih0w-.js.map +1 -0
- package/dashboard/dist/assets/{McpQueryPage-BBcG_UuU.js → McpQueryPage-U_wNXow2.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-BBcG_UuU.js.map → McpQueryPage-U_wNXow2.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-C6xw0kBg.js → McpRunDetailPage-DtKoJq6J.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-C6xw0kBg.js.map → McpRunDetailPage-DtKoJq6J.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-CRQqrF4Q.js → McpRunsPage-CnYtfGhf.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-CRQqrF4Q.js.map → McpRunsPage-CnYtfGhf.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-DADRJOUl.js → NamespacePill-DwM5pNA4.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-DADRJOUl.js.map → NamespacePill-DwM5pNA4.js.map} +1 -1
- package/dashboard/dist/assets/{OperationsPage-Dlfwzf-0.js → OperationsPage-C7hupIer.js} +2 -2
- package/dashboard/dist/assets/{OperationsPage-Dlfwzf-0.js.map → OperationsPage-C7hupIer.js.map} +1 -1
- package/dashboard/dist/assets/{OperatorDashboard-DoiDGZ9R.js → OperatorDashboard-fVuLn2D5.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-DoiDGZ9R.js.map → OperatorDashboard-fVuLn2D5.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-Csq434-v.js → PageHeader-CMd3f4bz.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-Csq434-v.js.map → PageHeader-CMd3f4bz.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-CSnk1p5A.js → PageHeaderWithStats-DHj8Mqox.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-CSnk1p5A.js.map → PageHeaderWithStats-DHj8Mqox.js.map} +1 -1
- package/dashboard/dist/assets/{PersonaDetailPage-BzbA4Prr.js → PersonaDetailPage-C-4o8lPw.js} +2 -2
- package/dashboard/dist/assets/{PersonaDetailPage-BzbA4Prr.js.map → PersonaDetailPage-C-4o8lPw.js.map} +1 -1
- package/dashboard/dist/assets/{PersonasPage-B36yn4hU.js → PersonasPage-BL1FwLmv.js} +2 -2
- package/dashboard/dist/assets/{PersonasPage-B36yn4hU.js.map → PersonasPage-BL1FwLmv.js.map} +1 -1
- package/dashboard/dist/assets/{PortalPage-BkD4YfDf.js → PortalPage-tJG5sgb1.js} +2 -2
- package/dashboard/dist/assets/{PortalPage-BkD4YfDf.js.map → PortalPage-tJG5sgb1.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-BT0eiBWY.js → ProcessDetailPage-Dz_jY4Yt.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-BT0eiBWY.js.map → ProcessDetailPage-Dz_jY4Yt.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-BEqeeNae.js → ProcessesListPage-Cbn9cter.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-BEqeeNae.js.map → ProcessesListPage-Cbn9cter.js.map} +1 -1
- package/dashboard/dist/assets/ResolverForm-Bk8iXQ6-.js +8 -0
- package/dashboard/dist/assets/ResolverForm-Bk8iXQ6-.js.map +1 -0
- package/dashboard/dist/assets/RoleDetailPage-owPi6gI9.js +8 -0
- package/dashboard/dist/assets/RoleDetailPage-owPi6gI9.js.map +1 -0
- package/dashboard/dist/assets/{RolePill-BafrASDX.js → RolePill-Di2CAHuD.js} +2 -2
- package/dashboard/dist/assets/{RolePill-BafrASDX.js.map → RolePill-Di2CAHuD.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-80quJU5-.js → RolesPage-NwmMvZFa.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-80quJU5-.js.map → RolesPage-NwmMvZFa.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-BoePhWVD.js → RunAsSelector-tgEooTyU.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-BoePhWVD.js.map → RunAsSelector-tgEooTyU.js.map} +1 -1
- package/dashboard/dist/assets/ScanCodesPage-CUv_8ic6.js +2 -0
- package/dashboard/dist/assets/ScanCodesPage-CUv_8ic6.js.map +1 -0
- package/dashboard/dist/assets/ScanSchemeDetailPage-YHqbu7oK.js +2 -0
- package/dashboard/dist/assets/ScanSchemeDetailPage-YHqbu7oK.js.map +1 -0
- package/dashboard/dist/assets/ScanStationPage-B275l6r_.js +2 -0
- package/dashboard/dist/assets/ScanStationPage-B275l6r_.js.map +1 -0
- package/dashboard/dist/assets/{ScopeBadge-b6MIBiYD.js → ScopeBadge-CSItPVOH.js} +2 -2
- package/dashboard/dist/assets/{ScopeBadge-b6MIBiYD.js.map → ScopeBadge-CSItPVOH.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-9ITrei_d.js → StickyPagination-CsTI_gvJ.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-9ITrei_d.js.map → StickyPagination-CsTI_gvJ.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-BkYvEHFY.js → StreamMessageDetail-auRlvjTh.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-BkYvEHFY.js.map → StreamMessageDetail-auRlvjTh.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-B1rotFix.js → SwimlaneTimeline-BLoVHYAd.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-B1rotFix.js.map → SwimlaneTimeline-BLoVHYAd.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-ZrIIrEUP.js → TagInput-CAgdLN_b.js} +2 -2
- package/dashboard/dist/assets/{TagInput-ZrIIrEUP.js.map → TagInput-CAgdLN_b.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-zhn_U_eC.js → TaskDetailPage-BPsZ1bK_.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-zhn_U_eC.js.map → TaskDetailPage-BPsZ1bK_.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-DkQ1uc6y.js → TaskQueuePill-h0oorVMe.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-DkQ1uc6y.js.map → TaskQueuePill-h0oorVMe.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-CNbNeagx.js → TasksListPage-BNGM74yT.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-CNbNeagx.js.map → TasksListPage-BNGM74yT.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-0YVMBwls.js → TimeAgo-BnAHXGK7.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-0YVMBwls.js.map → TimeAgo-BnAHXGK7.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-DB7r3np8.js → TimestampCell-J4yae6mc.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-DB7r3np8.js.map → TimestampCell-J4yae6mc.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-Baxm02L6.js → ToolPill-CQCsVdzD.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-Baxm02L6.js.map → ToolPill-CQCsVdzD.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-BLfA02wv.js → ToolTestPanel-BdzzgXDu.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-BLfA02wv.js.map → ToolTestPanel-BdzzgXDu.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-eAqPKShq.js → TopicDetailPage-DJKg8lb7.js} +4 -4
- package/dashboard/dist/assets/{TopicDetailPage-eAqPKShq.js.map → TopicDetailPage-DJKg8lb7.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-BNNWD-zl.js → TopicsPage-DQV0WxG6.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-BNNWD-zl.js.map → TopicsPage-DQV0WxG6.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-Pw46tO_k.js → UserName-Ct09nGh5.js} +2 -2
- package/dashboard/dist/assets/{UserName-Pw46tO_k.js.map → UserName-Ct09nGh5.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-DvA51lT3.js → WorkflowExecutionPage-B5WJOsee.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-DvA51lT3.js.map → WorkflowExecutionPage-B5WJOsee.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-ZepbO_ny.js → WorkflowPill-Dg7BDTyM.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-ZepbO_ny.js.map → WorkflowPill-Dg7BDTyM.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-DFDTwnhn.js → WorkflowsDashboard-Hc87TRgD.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-DFDTwnhn.js.map → WorkflowsDashboard-Hc87TRgD.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-5i2Tcy-p.js → WorkflowsOverview-CJQbyOj-.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-5i2Tcy-p.js.map → WorkflowsOverview-CJQbyOj-.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-p4VEYZS0.js → YamlWorkflowDetailPage-BMeED677.js} +8 -8
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-p4VEYZS0.js.map → YamlWorkflowDetailPage-BMeED677.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-Cq-hMVxl.js → YamlWorkflowsPage-BMMg0Id6.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-Cq-hMVxl.js.map → YamlWorkflowsPage-BMMg0Id6.js.map} +1 -1
- package/dashboard/dist/assets/{agents-EyBPJMYa.js → agents-CdFkgFvg.js} +2 -2
- package/dashboard/dist/assets/{agents-EyBPJMYa.js.map → agents-CdFkgFvg.js.map} +1 -1
- package/dashboard/dist/assets/{bots-DX6jjqtF.js → bots-Mj3LHWWg.js} +2 -2
- package/dashboard/dist/assets/{bots-DX6jjqtF.js.map → bots-Mj3LHWWg.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-ZdLv9xBQ.js → capabilities-Dw1ObK2-.js} +2 -2
- package/dashboard/dist/assets/{capabilities-ZdLv9xBQ.js.map → capabilities-Dw1ObK2-.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-CpvZaJuw.js → controlplane-DcNE1APv.js} +2 -2
- package/dashboard/dist/assets/{controlplane-CpvZaJuw.js.map → controlplane-DcNE1APv.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-Mvsze5eV.js → escalation-columns-D364Trpy.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-Mvsze5eV.js.map → escalation-columns-D364Trpy.js.map} +1 -1
- package/dashboard/dist/assets/{index-Cn8uJsd7.js → index-B3veJ698.js} +2 -2
- package/dashboard/dist/assets/{index-Cn8uJsd7.js.map → index-B3veJ698.js.map} +1 -1
- package/dashboard/dist/assets/index-BQeHJHYN.js +67 -0
- package/dashboard/dist/assets/index-BQeHJHYN.js.map +1 -0
- package/dashboard/dist/assets/{index-D-Xs4rAW.js → index-BRGTmoYY.js} +2 -2
- package/dashboard/dist/assets/{index-D-Xs4rAW.js.map → index-BRGTmoYY.js.map} +1 -1
- package/dashboard/dist/assets/{index-DkrBFFSO.js → index-Bd6-KmBE.js} +3 -3
- package/dashboard/dist/assets/{index-DkrBFFSO.js.map → index-Bd6-KmBE.js.map} +1 -1
- package/dashboard/dist/assets/{index-Bhb89oHH.js → index-BiVce3n0.js} +2 -2
- package/dashboard/dist/assets/{index-Bhb89oHH.js.map → index-BiVce3n0.js.map} +1 -1
- package/dashboard/dist/assets/{index-z5Hpeidu.js → index-BmNAVd4s.js} +2 -2
- package/dashboard/dist/assets/{index-z5Hpeidu.js.map → index-BmNAVd4s.js.map} +1 -1
- package/dashboard/dist/assets/{index-BbCjGO3M.js → index-Bq5XNEzD.js} +2 -2
- package/dashboard/dist/assets/{index-BbCjGO3M.js.map → index-Bq5XNEzD.js.map} +1 -1
- package/dashboard/dist/assets/{index-DGmzBY9D.js → index-DPStdq4T.js} +2 -2
- package/dashboard/dist/assets/{index-DGmzBY9D.js.map → index-DPStdq4T.js.map} +1 -1
- package/dashboard/dist/assets/index-DcppjzP5.css +1 -0
- package/dashboard/dist/assets/{index-PfSRKerc.js → index-DkcI4V_H.js} +2 -2
- package/dashboard/dist/assets/{index-PfSRKerc.js.map → index-DkcI4V_H.js.map} +1 -1
- package/dashboard/dist/assets/{index-oFKUFGQL.js → index-Dyotfa_k.js} +2 -2
- package/dashboard/dist/assets/{index-oFKUFGQL.js.map → index-Dyotfa_k.js.map} +1 -1
- package/dashboard/dist/assets/{index-Eo-l3Lqo.js → index-MDD4JYtu.js} +2 -2
- package/dashboard/dist/assets/{index-Eo-l3Lqo.js.map → index-MDD4JYtu.js.map} +1 -1
- package/dashboard/dist/assets/index-WSm8bgE7.js +3 -0
- package/dashboard/dist/assets/index-WSm8bgE7.js.map +1 -0
- package/dashboard/dist/assets/{index-DZhtP2Ok.js → index-eDm6cz0F.js} +2 -2
- package/dashboard/dist/assets/{index-DZhtP2Ok.js.map → index-eDm6cz0F.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-CgloEkaf.js → knowledge-Bafewjxk.js} +2 -2
- package/dashboard/dist/assets/{knowledge-CgloEkaf.js.map → knowledge-Bafewjxk.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-o-sGX2ST.js → mcp-query-BzTtIG90.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-o-sGX2ST.js.map → mcp-query-BzTtIG90.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-Bix7QYO7.js → mcp-vLIutbnc.js} +2 -2
- package/dashboard/dist/assets/{mcp-Bix7QYO7.js.map → mcp-vLIutbnc.js.map} +1 -1
- package/dashboard/dist/assets/{personas-30azkJ_s.js → personas-WD40EDS_.js} +2 -2
- package/dashboard/dist/assets/{personas-30azkJ_s.js.map → personas-WD40EDS_.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-Bfk2GFK7.js → pipelines-DFwULWs3.js} +2 -2
- package/dashboard/dist/assets/{pipelines-Bfk2GFK7.js.map → pipelines-DFwULWs3.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-CpdopewW.js → tasks-La_-WAUn.js} +2 -2
- package/dashboard/dist/assets/{tasks-CpdopewW.js.map → tasks-La_-WAUn.js.map} +1 -1
- package/dashboard/dist/assets/{topics--bvCV9Yp.js → topics-CzYMlvqm.js} +2 -2
- package/dashboard/dist/assets/{topics--bvCV9Yp.js.map → topics-CzYMlvqm.js.map} +1 -1
- package/dashboard/dist/assets/{useDebouncedValue-Da0lPmj3.js → useDebouncedValue-C0FQyANW.js} +2 -2
- package/dashboard/dist/assets/{useDebouncedValue-Da0lPmj3.js.map → useDebouncedValue-C0FQyANW.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-DD_Esl6n.js → useNamespace-WW_XjAb3.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-DD_Esl6n.js.map → useNamespace-WW_XjAb3.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-AeAhZJJk.js → useYamlActivityEvents-B3YxPeFt.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-AeAhZJJk.js.map → useYamlActivityEvents-B3YxPeFt.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-B7OcUM6b.js → vendor-icons-CeVtUKyP.js} +2 -2
- package/dashboard/dist/assets/{vendor-icons-B7OcUM6b.js.map → vendor-icons-CeVtUKyP.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-BHAOOGvn.js → yaml-workflows-COHOjdhF.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-BHAOOGvn.js.map → yaml-workflows-COHOjdhF.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/accumulator.md +37 -1
- package/docs/api/mcp/admin.md +3 -2
- package/docs/api/sdk/escalations.md +6 -0
- package/docs/api/sdk/exports.md +1 -1
- package/docs/api/sdk/workflows.md +23 -4
- package/docs/cli.md +1 -1
- package/docs/dashboard.md +2 -2
- package/docs/faceted-routing.md +2 -0
- package/docs/hitl/x-lt-scan.md +51 -0
- package/docs/hitl/x-lt-widget.md +5 -0
- package/docs/hitl-guide.md +2 -0
- package/docs/scan-codes.md +252 -19
- package/package.json +1 -1
- package/build/types/scan-code.d.ts +0 -308
- package/dashboard/dist/assets/AvailableEscalationsPage-CpbC8oTN.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-CpbC8oTN.js.map +0 -1
- package/dashboard/dist/assets/CapabilitiesPage-RGgD3YZO.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-BaexQMK6.js +0 -2
- package/dashboard/dist/assets/EscalationSchemaPage-W5r0A1kj.js +0 -9
- package/dashboard/dist/assets/EscalationSchemaPage-W5r0A1kj.js.map +0 -1
- package/dashboard/dist/assets/EscalationTimeline-DKyTz6sM.js +0 -2
- package/dashboard/dist/assets/EscalationTimeline-DKyTz6sM.js.map +0 -1
- package/dashboard/dist/assets/McpQueryDetailPage-DYJr16l7.js +0 -5
- package/dashboard/dist/assets/McpQueryDetailPage-DYJr16l7.js.map +0 -1
- package/dashboard/dist/assets/ResolverForm-BwgjIX97.js +0 -7
- package/dashboard/dist/assets/ResolverForm-BwgjIX97.js.map +0 -1
- package/dashboard/dist/assets/RoleDetailPage-BFuLNJXa.js +0 -8
- package/dashboard/dist/assets/RoleDetailPage-BFuLNJXa.js.map +0 -1
- package/dashboard/dist/assets/ScanCodesPage-Cu1bKEix.js +0 -2
- package/dashboard/dist/assets/ScanCodesPage-Cu1bKEix.js.map +0 -1
- package/dashboard/dist/assets/ScanSchemeDetailPage-D9AbS6UY.js +0 -2
- package/dashboard/dist/assets/ScanSchemeDetailPage-D9AbS6UY.js.map +0 -1
- package/dashboard/dist/assets/ScanStationPage-gcZGefvT.js +0 -2
- package/dashboard/dist/assets/ScanStationPage-gcZGefvT.js.map +0 -1
- package/dashboard/dist/assets/index-B_GT23TE.js +0 -3
- package/dashboard/dist/assets/index-B_GT23TE.js.map +0 -1
- package/dashboard/dist/assets/index-CgUcYveM.js +0 -67
- package/dashboard/dist/assets/index-CgUcYveM.js.map +0 -1
- package/dashboard/dist/assets/index-Dh8MJl3L.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccumulatedItem, LTEscalationRecord } from '../../types';
|
|
1
|
+
import type { AccumulatedItem, ContainerSelector, LTEscalationRecord } from '../../types';
|
|
2
2
|
import type { LTApiAuth, LTApiResult } from '../../types/sdk';
|
|
3
3
|
/** Names the second row of a reciprocal add or removal. Exactly one selector. */
|
|
4
4
|
export interface ReciprocalInput {
|
|
@@ -16,6 +16,11 @@ export interface AccumulateItemInput {
|
|
|
16
16
|
metadata?: Record<string, any>;
|
|
17
17
|
assertClaim?: boolean;
|
|
18
18
|
reciprocal?: ReciprocalInput;
|
|
19
|
+
/**
|
|
20
|
+
* The person who caused the add, when a trusted service performs it for
|
|
21
|
+
* them. Recorded as the entry's actor; the caller's rights still gate the write.
|
|
22
|
+
*/
|
|
23
|
+
initiatedBy?: string;
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
21
26
|
* Add ONE item to an accumulator escalation (a `conditional` wait declared
|
|
@@ -59,10 +64,14 @@ export declare function accumulateItemByMetadata(input: {
|
|
|
59
64
|
key: string;
|
|
60
65
|
value: string;
|
|
61
66
|
restrictRoles?: string[];
|
|
67
|
+
/** Narrows the container beyond the facet: type, subtype, extra facet guards. */
|
|
68
|
+
container?: ContainerSelector;
|
|
62
69
|
} & Omit<AccumulateItemInput, 'assertClaim'>, auth: LTApiAuth): Promise<LTApiResult>;
|
|
63
70
|
export interface RemoveItemInput {
|
|
64
71
|
itemKey: string;
|
|
65
72
|
reciprocal?: Omit<ReciprocalInput, 'payload'>;
|
|
73
|
+
/** The person who caused the removal; see {@link AccumulateItemInput.initiatedBy}. */
|
|
74
|
+
initiatedBy?: string;
|
|
66
75
|
}
|
|
67
76
|
/**
|
|
68
77
|
* Remove ONE held item from a pending accumulator escalation, one guarded
|
|
@@ -44,6 +44,8 @@ exports.getEscalationItems = getEscalationItems;
|
|
|
44
44
|
exports.getEscalationItemsBySignalKey = getEscalationItemsBySignalKey;
|
|
45
45
|
const escalationService = __importStar(require("../../services/escalation"));
|
|
46
46
|
const resolver_validation_1 = require("../../services/escalation/resolver-validation");
|
|
47
|
+
const userService = __importStar(require("../../services/user"));
|
|
48
|
+
const uuid_1 = require("../../lib/uuid");
|
|
47
49
|
const enforcement_cache_1 = require("../../services/role/enforcement-cache");
|
|
48
50
|
const escalation_1 = require("../../types/escalation");
|
|
49
51
|
const helpers_1 = require("./helpers");
|
|
@@ -66,23 +68,73 @@ function isBatch(escalation) {
|
|
|
66
68
|
return Array.isArray(escalation.metadata?.[escalation_1.ESCALATION_BATCH_KEYS.PENDING]);
|
|
67
69
|
}
|
|
68
70
|
const NO_ACCUMULATOR_FOR_FACET = 'No pending accumulator found for this metadata';
|
|
71
|
+
/** The add's item-key error, or null when the key is usable. */
|
|
72
|
+
function itemKeyError(itemKey) {
|
|
73
|
+
if (!itemKey)
|
|
74
|
+
return { status: 400, error: 'itemKey is required' };
|
|
75
|
+
if (typeof itemKey !== 'string' || itemKey.length > escalation_1.ESCALATION_ITEM_KEY_MAX_LENGTH) {
|
|
76
|
+
return { status: 400, error: `itemKey must be a string of at most ${escalation_1.ESCALATION_ITEM_KEY_MAX_LENGTH} characters` };
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
69
80
|
/**
|
|
70
81
|
* The container a facet names: the highest priority pending row that carries
|
|
71
82
|
* the facet AND the accumulator declaration, within the given roles (null =
|
|
72
|
-
* unscoped). A release or remediation row sharing
|
|
73
|
-
*
|
|
83
|
+
* unscoped) and the selector's guards. A release or remediation row sharing
|
|
84
|
+
* the facet is never a candidate, and neither is the add's own reciprocal row,
|
|
85
|
+
* so a by-facet add lands on the open container or nowhere.
|
|
74
86
|
*/
|
|
75
|
-
async function pickAccumulatorByFacet(key, value, roles) {
|
|
87
|
+
async function pickAccumulatorByFacet(key, value, roles, selector, excludeId) {
|
|
76
88
|
if (roles !== null && roles.length === 0)
|
|
77
89
|
return null;
|
|
78
90
|
const found = await escalationService.searchByFacets({
|
|
79
91
|
roles: roles ?? undefined,
|
|
80
|
-
|
|
92
|
+
types: selector?.types,
|
|
93
|
+
subtypes: selector?.subtypes,
|
|
94
|
+
facets: { ...selector?.facets, [key]: value },
|
|
81
95
|
status: 'pending',
|
|
82
96
|
exists: [escalation_1.ESCALATION_ACCUMULATE_KEYS.COUNT],
|
|
83
|
-
limit: 1,
|
|
97
|
+
limit: excludeId ? 2 : 1,
|
|
84
98
|
});
|
|
85
|
-
return found.escalations
|
|
99
|
+
return found.escalations.find((row) => row.id !== excludeId) ?? null;
|
|
100
|
+
}
|
|
101
|
+
const isStringList = (v) => Array.isArray(v) && v.every((s) => typeof s === 'string' && s.length > 0);
|
|
102
|
+
/** The selector's shape error, or null when it is absent or well formed. */
|
|
103
|
+
function containerSelectorError(selector) {
|
|
104
|
+
if (selector === undefined)
|
|
105
|
+
return null;
|
|
106
|
+
if (!selector || typeof selector !== 'object' || Array.isArray(selector))
|
|
107
|
+
return 'container must be an object';
|
|
108
|
+
if (selector.types !== undefined && !isStringList(selector.types))
|
|
109
|
+
return 'container.types must be an array of strings';
|
|
110
|
+
if (selector.subtypes !== undefined && !isStringList(selector.subtypes))
|
|
111
|
+
return 'container.subtypes must be an array of strings';
|
|
112
|
+
if (selector.facets !== undefined && (!selector.facets || typeof selector.facets !== 'object' || Array.isArray(selector.facets))) {
|
|
113
|
+
return 'container.facets must be an object';
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function isNarrowed(selector) {
|
|
118
|
+
return !!(selector?.types?.length || selector?.subtypes?.length
|
|
119
|
+
|| (selector?.facets && Object.keys(selector.facets).length));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The entry's actor: the caller, or the user a global-access caller names as
|
|
123
|
+
* the initiator. Attribution only; it never widens what the caller may write.
|
|
124
|
+
*/
|
|
125
|
+
async function resolveActor(initiatedBy, auth) {
|
|
126
|
+
if (initiatedBy === undefined || initiatedBy === auth.userId)
|
|
127
|
+
return { actor: auth.userId };
|
|
128
|
+
if (typeof initiatedBy !== 'string' || !(0, uuid_1.isUuid)(initiatedBy)) {
|
|
129
|
+
return { error: { status: 400, error: 'initiatedBy must be a user id' } };
|
|
130
|
+
}
|
|
131
|
+
if (!(await userService.hasGlobalEscalationAccess(auth.userId))) {
|
|
132
|
+
return { error: { status: 403, error: 'initiatedBy requires global escalation access' } };
|
|
133
|
+
}
|
|
134
|
+
if (!(await userService.getUser(initiatedBy))) {
|
|
135
|
+
return { error: { status: 400, error: 'initiatedBy names no user' } };
|
|
136
|
+
}
|
|
137
|
+
return { actor: initiatedBy };
|
|
86
138
|
}
|
|
87
139
|
function reciprocalSelectorCount(r) {
|
|
88
140
|
return (r.id ? 1 : 0) + (r.signalKey ? 1 : 0) + (r.key !== undefined ? 1 : 0);
|
|
@@ -156,15 +208,22 @@ async function gatePayload(escalation, payload, userId) {
|
|
|
156
208
|
async function accumulateItem(input, auth) {
|
|
157
209
|
try {
|
|
158
210
|
const { id, itemKey, metadata } = input;
|
|
159
|
-
|
|
160
|
-
|
|
211
|
+
const keyError = itemKeyError(itemKey);
|
|
212
|
+
if (keyError)
|
|
213
|
+
return keyError;
|
|
214
|
+
const initiator = await resolveActor(input.initiatedBy, auth);
|
|
215
|
+
if ('error' in initiator)
|
|
216
|
+
return initiator.error;
|
|
161
217
|
const escalation = await escalationService.getEscalation(id);
|
|
162
218
|
if (!escalation)
|
|
163
219
|
return { status: 404, error: 'Escalation not found' };
|
|
164
|
-
if (escalation.status === 'cancelled')
|
|
165
|
-
return { status: 409, error: 'Escalation is cancelled' };
|
|
166
|
-
|
|
167
|
-
|
|
220
|
+
if (escalation.status === 'cancelled') {
|
|
221
|
+
return { status: 409, error: 'Escalation is cancelled', data: { error: 'Escalation is cancelled', outcome: 'already-cancelled' } };
|
|
222
|
+
}
|
|
223
|
+
if (escalation.status !== 'pending') {
|
|
224
|
+
const error = 'Escalation not available for accumulation';
|
|
225
|
+
return { status: 409, error, data: { error, outcome: `already-${escalation.status}` } };
|
|
226
|
+
}
|
|
168
227
|
if (!isAccumulator(escalation))
|
|
169
228
|
return { status: 400, error: 'Escalation is not an accumulator' };
|
|
170
229
|
if (await (0, helpers_1.assertReadAccess)(auth.userId, escalation)) {
|
|
@@ -183,7 +242,7 @@ async function accumulateItem(input, auth) {
|
|
|
183
242
|
itemKey,
|
|
184
243
|
payload: payload.payload,
|
|
185
244
|
metadata: { ...metadata, resolved_by: auth.userId },
|
|
186
|
-
actor:
|
|
245
|
+
actor: initiator.actor,
|
|
187
246
|
assertClaim: input.assertClaim ? auth.userId : undefined,
|
|
188
247
|
resolvedBy: await (0, resolve_1.resolverIdentity)(auth),
|
|
189
248
|
reciprocal: reciprocal.reciprocal,
|
|
@@ -204,8 +263,12 @@ async function accumulateItemBySignalKey(input, auth) {
|
|
|
204
263
|
const { signalKey, itemKey, metadata } = input;
|
|
205
264
|
if (!signalKey)
|
|
206
265
|
return { status: 400, error: 'signalKey is required' };
|
|
207
|
-
|
|
208
|
-
|
|
266
|
+
const keyError = itemKeyError(itemKey);
|
|
267
|
+
if (keyError)
|
|
268
|
+
return keyError;
|
|
269
|
+
const initiator = await resolveActor(input.initiatedBy, auth);
|
|
270
|
+
if ('error' in initiator)
|
|
271
|
+
return initiator.error;
|
|
209
272
|
const escalation = await escalationService.getEscalationBySignalKey(signalKey);
|
|
210
273
|
if (!escalation)
|
|
211
274
|
return { status: 404, error: 'Escalation not found' };
|
|
@@ -226,7 +289,7 @@ async function accumulateItemBySignalKey(input, auth) {
|
|
|
226
289
|
itemKey,
|
|
227
290
|
payload: payload.payload,
|
|
228
291
|
metadata: { ...metadata, resolved_by: auth.userId },
|
|
229
|
-
actor:
|
|
292
|
+
actor: initiator.actor,
|
|
230
293
|
resolvedBy: await (0, resolve_1.resolverIdentity)(auth),
|
|
231
294
|
reciprocal: reciprocal.reciprocal,
|
|
232
295
|
});
|
|
@@ -252,8 +315,15 @@ async function accumulateItemByMetadata(input, auth) {
|
|
|
252
315
|
const { key, value, itemKey, metadata } = input;
|
|
253
316
|
if (!key || !value)
|
|
254
317
|
return { status: 400, error: 'key and value are required' };
|
|
255
|
-
|
|
256
|
-
|
|
318
|
+
const keyError = itemKeyError(itemKey);
|
|
319
|
+
if (keyError)
|
|
320
|
+
return keyError;
|
|
321
|
+
const initiator = await resolveActor(input.initiatedBy, auth);
|
|
322
|
+
if ('error' in initiator)
|
|
323
|
+
return initiator.error;
|
|
324
|
+
const selectorError = containerSelectorError(input.container);
|
|
325
|
+
if (selectorError)
|
|
326
|
+
return { status: 400, error: selectorError };
|
|
257
327
|
const writeScope = await (0, helpers_1.getEscalationWriteScope)(auth.userId);
|
|
258
328
|
const allowedRoles = (0, metadata_1.restrictScopeRoles)(writeScope.allRoles, writeScope.global, input.restrictRoles);
|
|
259
329
|
const reciprocal = await gateReciprocal(input.reciprocal, auth);
|
|
@@ -261,13 +331,17 @@ async function accumulateItemByMetadata(input, auth) {
|
|
|
261
331
|
return reciprocal.error;
|
|
262
332
|
const resolvedBy = await (0, resolve_1.resolverIdentity)(auth);
|
|
263
333
|
const outcome = { ...metadata, resolved_by: auth.userId };
|
|
334
|
+
// A narrowed selector picks in Long Tail, then adds by id; the by-id
|
|
335
|
+
// statement re-checks pending under its lock, and type, subtype and the
|
|
336
|
+
// accumulator declaration are fixed at creation.
|
|
264
337
|
const enforcing = await (0, enforcement_cache_1.getEnforcingRoles)();
|
|
265
|
-
|
|
338
|
+
const narrowed = isNarrowed(input.container);
|
|
339
|
+
if (!narrowed && (enforcing.size === 0 || input.payload === undefined)) {
|
|
266
340
|
const result = await escalationService.accumulateItemByMetadata(key, value, {
|
|
267
341
|
itemKey,
|
|
268
342
|
payload: input.payload,
|
|
269
343
|
metadata: outcome,
|
|
270
|
-
actor:
|
|
344
|
+
actor: initiator.actor,
|
|
271
345
|
resolvedBy,
|
|
272
346
|
roles: allowedRoles ?? undefined,
|
|
273
347
|
reciprocal: reciprocal.reciprocal,
|
|
@@ -276,7 +350,7 @@ async function accumulateItemByMetadata(input, auth) {
|
|
|
276
350
|
return { status: 404, error: NO_ACCUMULATOR_FOR_FACET };
|
|
277
351
|
return accumulateOutcomeResult(result, itemKey, result.escalation?.workflow_id ?? null);
|
|
278
352
|
}
|
|
279
|
-
const row = await pickAccumulatorByFacet(key, value, allowedRoles);
|
|
353
|
+
const row = await pickAccumulatorByFacet(key, value, allowedRoles, input.container, reciprocal.reciprocal?.id);
|
|
280
354
|
if (!row)
|
|
281
355
|
return { status: 404, error: NO_ACCUMULATOR_FOR_FACET };
|
|
282
356
|
if (!isAccumulator(row))
|
|
@@ -288,7 +362,7 @@ async function accumulateItemByMetadata(input, auth) {
|
|
|
288
362
|
itemKey,
|
|
289
363
|
payload: payload.payload,
|
|
290
364
|
metadata: outcome,
|
|
291
|
-
actor:
|
|
365
|
+
actor: initiator.actor,
|
|
292
366
|
resolvedBy,
|
|
293
367
|
reciprocal: reciprocal.reciprocal,
|
|
294
368
|
});
|
|
@@ -311,6 +385,9 @@ async function removeItem(input, auth) {
|
|
|
311
385
|
const { id, itemKey } = input;
|
|
312
386
|
if (!itemKey)
|
|
313
387
|
return { status: 400, error: 'itemKey is required' };
|
|
388
|
+
const initiator = await resolveActor(input.initiatedBy, auth);
|
|
389
|
+
if ('error' in initiator)
|
|
390
|
+
return initiator.error;
|
|
314
391
|
const escalation = await escalationService.getEscalation(id);
|
|
315
392
|
if (!escalation)
|
|
316
393
|
return { status: 404, error: 'Escalation not found' };
|
|
@@ -328,7 +405,7 @@ async function removeItem(input, auth) {
|
|
|
328
405
|
if ('error' in reciprocal)
|
|
329
406
|
return reciprocal.error;
|
|
330
407
|
const result = await escalationService.removeAccumulatedItem(escalation.id, {
|
|
331
|
-
itemKey, actor:
|
|
408
|
+
itemKey, actor: initiator.actor, reciprocal: reciprocal.reciprocal,
|
|
332
409
|
});
|
|
333
410
|
return removeOutcomeResult(result, itemKey);
|
|
334
411
|
}
|
|
@@ -343,6 +420,9 @@ async function removeItemBySignalKey(input, auth) {
|
|
|
343
420
|
return { status: 400, error: 'signalKey is required' };
|
|
344
421
|
if (!itemKey)
|
|
345
422
|
return { status: 400, error: 'itemKey is required' };
|
|
423
|
+
const initiator = await resolveActor(input.initiatedBy, auth);
|
|
424
|
+
if ('error' in initiator)
|
|
425
|
+
return initiator.error;
|
|
346
426
|
const escalation = await escalationService.getEscalationBySignalKey(signalKey);
|
|
347
427
|
if (!escalation)
|
|
348
428
|
return { status: 404, error: 'Escalation not found' };
|
|
@@ -357,7 +437,7 @@ async function removeItemBySignalKey(input, auth) {
|
|
|
357
437
|
if ('error' in reciprocal)
|
|
358
438
|
return reciprocal.error;
|
|
359
439
|
const result = await escalationService.removeAccumulatedItemBySignalKey(signalKey, {
|
|
360
|
-
itemKey, actor:
|
|
440
|
+
itemKey, actor: initiator.actor, reciprocal: reciprocal.reciprocal,
|
|
361
441
|
});
|
|
362
442
|
return removeOutcomeResult(result, itemKey);
|
|
363
443
|
}
|
|
@@ -372,13 +452,16 @@ async function removeItemByMetadata(input, auth) {
|
|
|
372
452
|
return { status: 400, error: 'key and value are required' };
|
|
373
453
|
if (!itemKey)
|
|
374
454
|
return { status: 400, error: 'itemKey is required' };
|
|
455
|
+
const initiator = await resolveActor(input.initiatedBy, auth);
|
|
456
|
+
if ('error' in initiator)
|
|
457
|
+
return initiator.error;
|
|
375
458
|
const writeScope = await (0, helpers_1.getEscalationWriteScope)(auth.userId);
|
|
376
459
|
const allowedRoles = (0, metadata_1.restrictScopeRoles)(writeScope.allRoles, writeScope.global, input.restrictRoles);
|
|
377
460
|
const reciprocal = await gateReciprocal(input.reciprocal, auth);
|
|
378
461
|
if ('error' in reciprocal)
|
|
379
462
|
return reciprocal.error;
|
|
380
463
|
const result = await escalationService.removeAccumulatedItemByMetadata(key, value, {
|
|
381
|
-
itemKey, actor:
|
|
464
|
+
itemKey, actor: initiator.actor, roles: allowedRoles ?? undefined, reciprocal: reciprocal.reciprocal,
|
|
382
465
|
});
|
|
383
466
|
if (result.outcome === 'not-found')
|
|
384
467
|
return { status: 404, error: NO_ACCUMULATOR_FOR_FACET };
|
|
@@ -506,21 +589,21 @@ function accumulateOutcomeResult(result, itemKey, workflowId) {
|
|
|
506
589
|
},
|
|
507
590
|
};
|
|
508
591
|
case 'duplicate-item':
|
|
509
|
-
return { status: 409, error: 'Item already held', data: { error: 'Item already held', itemKey } };
|
|
592
|
+
return { status: 409, error: 'Item already held', data: { error: 'Item already held', itemKey, outcome: 'duplicate-item' } };
|
|
510
593
|
case 'full':
|
|
511
|
-
return { status: 409, error: 'Accumulator is full', data: { error: 'Accumulator is full', itemKey } };
|
|
594
|
+
return { status: 409, error: 'Accumulator is full', data: { error: 'Accumulator is full', itemKey, outcome: 'full' } };
|
|
512
595
|
case 'claimed-by-other':
|
|
513
|
-
return { status: 409, error: 'Escalation is claimed by another user' };
|
|
596
|
+
return { status: 409, error: 'Escalation is claimed by another user', data: { error: 'Escalation is claimed by another user', outcome: 'claimed-by-other' } };
|
|
514
597
|
case 'claim-expired':
|
|
515
|
-
return { status: 409, error: 'Your claim has expired; re-claim this escalation to add to it' };
|
|
598
|
+
return { status: 409, error: 'Your claim has expired; re-claim this escalation to add to it', data: { error: 'Your claim has expired; re-claim this escalation to add to it', outcome: 'claim-expired' } };
|
|
516
599
|
case 'not-found':
|
|
517
|
-
return { status: 404, error: 'Escalation not found' };
|
|
600
|
+
return { status: 404, error: 'Escalation not found', data: { error: 'Escalation not found', outcome: 'not-found' } };
|
|
518
601
|
case 'not-accumulator':
|
|
519
|
-
return { status: 400, error: 'Escalation is not an accumulator' };
|
|
602
|
+
return { status: 400, error: 'Escalation is not an accumulator', data: { error: 'Escalation is not an accumulator', outcome: 'not-accumulator' } };
|
|
520
603
|
case 'reciprocal-not-found':
|
|
521
|
-
return { status: 404, error: 'Reciprocal escalation not found' };
|
|
604
|
+
return { status: 404, error: 'Reciprocal escalation not found', data: { error: 'Reciprocal escalation not found', outcome: 'reciprocal-not-found' } };
|
|
522
605
|
case 'reciprocal-not-accumulator':
|
|
523
|
-
return { status: 400, error: 'Reciprocal escalation is not an accumulator' };
|
|
606
|
+
return { status: 400, error: 'Reciprocal escalation is not an accumulator', data: { error: 'Reciprocal escalation is not an accumulator', outcome: 'reciprocal-not-accumulator' } };
|
|
524
607
|
case 'reciprocal-duplicate':
|
|
525
608
|
return { status: 409, error: 'Reciprocal escalation already holds this container', data: { error: 'Reciprocal escalation already holds this container', outcome: result.outcome } };
|
|
526
609
|
case 'reciprocal-full':
|
|
@@ -528,7 +611,11 @@ function accumulateOutcomeResult(result, itemKey, workflowId) {
|
|
|
528
611
|
case 'reciprocal-terminal':
|
|
529
612
|
return { status: 409, error: 'Reciprocal escalation is no longer pending', data: { error: 'Reciprocal escalation is no longer pending', outcome: result.outcome } };
|
|
530
613
|
default:
|
|
531
|
-
return {
|
|
614
|
+
return {
|
|
615
|
+
status: 409,
|
|
616
|
+
error: 'Escalation not available for accumulation',
|
|
617
|
+
data: { error: 'Escalation not available for accumulation', outcome: result.outcome },
|
|
618
|
+
};
|
|
532
619
|
}
|
|
533
620
|
}
|
|
534
621
|
function removeOutcomeResult(result, itemKey) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ProvisionIfAbsent } from './helpers';
|
|
2
2
|
import type { LTApiAuth, LTApiResult } from '../../types/sdk';
|
|
3
|
+
import type { ScanAvailability } from '../../types';
|
|
3
4
|
/**
|
|
4
5
|
* Intersect the caller's scope roles with an optional caller-supplied
|
|
5
6
|
* restriction (e.g. a scan step's expected queues). Global scope with no
|
|
@@ -57,4 +58,8 @@ export declare function resolveByMetadata(input: {
|
|
|
57
58
|
metadata?: Record<string, any>;
|
|
58
59
|
restrictRoles?: string[];
|
|
59
60
|
extraFacets?: Record<string, any>;
|
|
61
|
+
/** Narrows the pick to a claim state; 'mine' is the resolver's live claim. */
|
|
62
|
+
availability?: ScanAvailability;
|
|
63
|
+
/** Resolves only this row, when it still matches every other condition. */
|
|
64
|
+
assertId?: string;
|
|
60
65
|
}, auth: LTApiAuth): Promise<LTApiResult>;
|
|
@@ -42,6 +42,7 @@ const helpers_1 = require("./helpers");
|
|
|
42
42
|
const resolver_validation_1 = require("../../services/escalation/resolver-validation");
|
|
43
43
|
const enforcement_cache_1 = require("../../services/role/enforcement-cache");
|
|
44
44
|
const resolve_1 = require("./resolve");
|
|
45
|
+
const uuid_1 = require("../../lib/uuid");
|
|
45
46
|
/**
|
|
46
47
|
* Intersect the caller's scope roles with an optional caller-supplied
|
|
47
48
|
* restriction (e.g. a scan step's expected queues). Global scope with no
|
|
@@ -138,6 +139,9 @@ async function resolveByMetadata(input, auth) {
|
|
|
138
139
|
if (!input.resolverPayload) {
|
|
139
140
|
return { status: 400, error: 'resolverPayload is required' };
|
|
140
141
|
}
|
|
142
|
+
if (input.assertId !== undefined && !(0, uuid_1.isUuid)(input.assertId)) {
|
|
143
|
+
return { status: 404, error: 'No pending escalation found for this metadata, or insufficient role permissions' };
|
|
144
|
+
}
|
|
141
145
|
const resolved = await (0, helpers_1.resolveAssignee)(input.assignee, auth);
|
|
142
146
|
if ('error' in resolved)
|
|
143
147
|
return resolved.error;
|
|
@@ -159,12 +163,12 @@ async function resolveByMetadata(input, auth) {
|
|
|
159
163
|
// writing; the payload validates here, then a second asserted pass claims
|
|
160
164
|
// + resolves the same row with pending re-checked inside the statement.
|
|
161
165
|
const enforcing = await (0, enforcement_cache_1.getEnforcingRoles)();
|
|
162
|
-
let result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, input.metadata, writeAllRoles, writeSelfRoles, enforcing.size > 0 ? [...enforcing] : null, null, input.extraFacets);
|
|
166
|
+
let result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, input.metadata, writeAllRoles, writeSelfRoles, enforcing.size > 0 ? [...enforcing] : null, input.assertId ?? null, input.extraFacets, input.availability);
|
|
163
167
|
if (result.outcome === 'validation_required' && result.row) {
|
|
164
168
|
const report = await (0, resolver_validation_1.checkResolverPayload)(result.row, input.resolverPayload, undefined, resolveUserId);
|
|
165
169
|
if (report)
|
|
166
170
|
return (0, resolve_1.validationFailure)(report);
|
|
167
|
-
result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, input.metadata, writeAllRoles, writeSelfRoles, null, result.row.id, input.extraFacets);
|
|
171
|
+
result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, input.metadata, writeAllRoles, writeSelfRoles, null, result.row.id, input.extraFacets, input.availability);
|
|
168
172
|
// The asserted row left pending between the two passes — a concurrent
|
|
169
173
|
// resolution won the row; surface it as the conflict it is.
|
|
170
174
|
if (result.outcome === 'not_found') {
|
|
@@ -70,10 +70,13 @@ async function resolveBatchItem(input, auth) {
|
|
|
70
70
|
const escalation = await escalationService.getEscalation(id);
|
|
71
71
|
if (!escalation)
|
|
72
72
|
return { status: 404, error: 'Escalation not found' };
|
|
73
|
-
if (escalation.status === 'cancelled')
|
|
74
|
-
return { status: 409, error: 'Escalation is cancelled' };
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
if (escalation.status === 'cancelled') {
|
|
74
|
+
return { status: 409, error: 'Escalation is cancelled', data: { error: 'Escalation is cancelled', outcome: 'already-cancelled' } };
|
|
75
|
+
}
|
|
76
|
+
if (escalation.status !== 'pending') {
|
|
77
|
+
const error = 'Escalation not available for resolution';
|
|
78
|
+
return { status: 409, error, data: { error, outcome: `already-${escalation.status}` } };
|
|
79
|
+
}
|
|
77
80
|
// Advisory shape gate — cheap, readable rejection. The SDK's guarded
|
|
78
81
|
// statement re-checks and is the atomic arbiter (outcome 'not-batch').
|
|
79
82
|
const pending = escalation.metadata?.[escalation_1.ESCALATION_BATCH_KEYS.PENDING];
|
|
@@ -228,20 +231,24 @@ function batchOutcomeResult(result, itemKey, workflowId) {
|
|
|
228
231
|
return {
|
|
229
232
|
status: 409,
|
|
230
233
|
error: 'Batch item already submitted',
|
|
231
|
-
data: { error: 'Batch item already submitted', itemKey },
|
|
234
|
+
data: { error: 'Batch item already submitted', itemKey, outcome: 'duplicate-item' },
|
|
232
235
|
};
|
|
233
236
|
case 'claimed-by-other':
|
|
234
|
-
return { status: 409, error: 'Escalation is claimed by another user' };
|
|
237
|
+
return { status: 409, error: 'Escalation is claimed by another user', data: { error: 'Escalation is claimed by another user', outcome: 'claimed-by-other' } };
|
|
235
238
|
case 'claim-expired':
|
|
236
|
-
return { status: 409, error: 'Your claim has expired — re-claim this escalation to resolve it' };
|
|
239
|
+
return { status: 409, error: 'Your claim has expired — re-claim this escalation to resolve it', data: { error: 'Your claim has expired — re-claim this escalation to resolve it', outcome: 'claim-expired' } };
|
|
237
240
|
case 'not-found':
|
|
238
|
-
return { status: 404, error: 'Escalation not found' };
|
|
241
|
+
return { status: 404, error: 'Escalation not found', data: { error: 'Escalation not found', outcome: 'not-found' } };
|
|
239
242
|
case 'unknown-item':
|
|
240
|
-
return { status: 400, error: 'itemKey is not in the declared batch' };
|
|
243
|
+
return { status: 400, error: 'itemKey is not in the declared batch', data: { error: 'itemKey is not in the declared batch', outcome: 'unknown-item' } };
|
|
241
244
|
case 'not-batch':
|
|
242
|
-
return { status: 400, error: 'Escalation is not a batch' };
|
|
245
|
+
return { status: 400, error: 'Escalation is not a batch', data: { error: 'Escalation is not a batch', outcome: 'not-batch' } };
|
|
243
246
|
default:
|
|
244
247
|
// already-resolved / already-cancelled / already-expired
|
|
245
|
-
return {
|
|
248
|
+
return {
|
|
249
|
+
status: 409,
|
|
250
|
+
error: 'Escalation not available for resolution',
|
|
251
|
+
data: { error: 'Escalation not available for resolution', outcome: result.outcome },
|
|
252
|
+
};
|
|
246
253
|
}
|
|
247
254
|
}
|
package/build/api/roles.js
CHANGED
|
@@ -49,6 +49,7 @@ exports.listRoleListSchemaVersions = listRoleListSchemaVersions;
|
|
|
49
49
|
exports.deleteRole = deleteRole;
|
|
50
50
|
const roleService = __importStar(require("../services/role"));
|
|
51
51
|
const types_1 = require("../services/role/types");
|
|
52
|
+
const badge_grant_1 = require("../services/role/badge-grant");
|
|
52
53
|
const facet_sql_1 = require("../services/escalation/facet-sql");
|
|
53
54
|
/**
|
|
54
55
|
* List all distinct role names in the system.
|
|
@@ -291,6 +292,9 @@ async function updateRole(input) {
|
|
|
291
292
|
return { status: 400, error: 'default_pins must be [{ label, url, badge? }] with dashboard-relative urls' };
|
|
292
293
|
}
|
|
293
294
|
}
|
|
295
|
+
const grantError = (0, badge_grant_1.badgeGrantError)(input.properties?.[types_1.ROLE_PROPERTY_KEYS.BADGE_GRANT]);
|
|
296
|
+
if (grantError)
|
|
297
|
+
return { status: 400, error: grantError };
|
|
294
298
|
if (input.portals != null && !isPortalList(input.portals)) {
|
|
295
299
|
return {
|
|
296
300
|
status: 400,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ScanGrantScope, type ScanScheme } from '../../types';
|
|
2
|
+
export interface GrantPolicy {
|
|
3
|
+
ttlSeconds: number;
|
|
4
|
+
maxUses: number;
|
|
5
|
+
scope: ScanGrantScope;
|
|
6
|
+
/** The station role whose policy applied; absent when the scheme's did. */
|
|
7
|
+
role?: string;
|
|
8
|
+
}
|
|
9
|
+
/** The policy a badge scanned on this device mints under. */
|
|
10
|
+
export declare function stationGrantPolicy(scheme: ScanScheme, stationUserId: string, stationRole?: string): Promise<GrantPolicy>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.stationGrantPolicy = stationGrantPolicy;
|
|
37
|
+
const roleService = __importStar(require("../../services/role"));
|
|
38
|
+
const userService = __importStar(require("../../services/user"));
|
|
39
|
+
const badge_grant_1 = require("../../services/role/badge-grant");
|
|
40
|
+
const types_1 = require("../../types");
|
|
41
|
+
function schemePolicy(scheme) {
|
|
42
|
+
return {
|
|
43
|
+
ttlSeconds: scheme.grant_ttl_seconds ?? 0,
|
|
44
|
+
maxUses: scheme.grant_max_uses ?? 0,
|
|
45
|
+
scope: scheme.grant_scope ?? types_1.SCAN_GRANT_SCOPES.ACTION,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** The policy a badge scanned on this device mints under. */
|
|
49
|
+
async function stationGrantPolicy(scheme, stationUserId, stationRole) {
|
|
50
|
+
const fallback = schemePolicy(scheme);
|
|
51
|
+
const user = await userService.getUser(stationUserId);
|
|
52
|
+
const memberRoles = (user?.roles ?? []).map((r) => r.role);
|
|
53
|
+
const candidates = stationRole
|
|
54
|
+
? memberRoles.filter((r) => r === stationRole)
|
|
55
|
+
: memberRoles;
|
|
56
|
+
const declared = [];
|
|
57
|
+
for (const role of candidates) {
|
|
58
|
+
const grant = (0, badge_grant_1.readBadgeGrant)(await roleService.getRoleProperties(role));
|
|
59
|
+
if (grant)
|
|
60
|
+
declared.push({ role, grant });
|
|
61
|
+
}
|
|
62
|
+
const distinct = new Set(declared.map((d) => JSON.stringify(d.grant)));
|
|
63
|
+
if (distinct.size !== 1)
|
|
64
|
+
return fallback;
|
|
65
|
+
const { role, grant } = declared[0];
|
|
66
|
+
return {
|
|
67
|
+
ttlSeconds: grant.ttl_seconds ?? fallback.ttlSeconds,
|
|
68
|
+
maxUses: grant.max_uses ?? fallback.maxUses,
|
|
69
|
+
scope: grant.scope ?? fallback.scope,
|
|
70
|
+
role,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -38,6 +38,7 @@ const scanCodeService = __importStar(require("../../services/scan-code"));
|
|
|
38
38
|
const escalationService = __importStar(require("../../services/escalation"));
|
|
39
39
|
const types_1 = require("../../types");
|
|
40
40
|
const identity_1 = require("./identity");
|
|
41
|
+
const grant_1 = require("./grant");
|
|
41
42
|
const context_1 = require("./context");
|
|
42
43
|
const locate_1 = require("./locate");
|
|
43
44
|
const verbs_1 = require("./verbs");
|
|
@@ -69,19 +70,19 @@ async function executeScanChoice(input, auth) {
|
|
|
69
70
|
data: { outcome: types_1.SCAN_OUTCOMES.UNCONFIGURED, error: 'the presented choice is no longer configured' },
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
|
-
// 2. The acting grant,
|
|
73
|
+
// 2. The acting grant, read (not spent) before anything reads or writes.
|
|
73
74
|
let effectiveAuth = auth;
|
|
74
|
-
let
|
|
75
|
+
let grant;
|
|
75
76
|
if (input.actingToken) {
|
|
76
|
-
const
|
|
77
|
-
if (!
|
|
77
|
+
const peeked = await (0, grant_1.peekGrant)(input.actingToken);
|
|
78
|
+
if (!peeked.ok) {
|
|
78
79
|
return {
|
|
79
80
|
status: 200,
|
|
80
|
-
data: { outcome: types_1.SCAN_OUTCOMES.NOT_PRIMED, notPrimed: rule.notPrimed, error:
|
|
81
|
+
data: { outcome: types_1.SCAN_OUTCOMES.NOT_PRIMED, notPrimed: rule.notPrimed, replayable: true, error: peeked.error },
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
|
-
effectiveAuth =
|
|
84
|
-
|
|
84
|
+
effectiveAuth = peeked.auth;
|
|
85
|
+
grant = peeked.grant;
|
|
85
86
|
}
|
|
86
87
|
// 3. The row anchors the target: the scheme's facet on the escalation the
|
|
87
88
|
// screen presented. A missing binding is loud misconfiguration.
|
|
@@ -100,7 +101,8 @@ async function executeScanChoice(input, auth) {
|
|
|
100
101
|
scannedAt: new Date().toISOString(),
|
|
101
102
|
auth: effectiveAuth,
|
|
102
103
|
stationAuth: auth,
|
|
103
|
-
acting,
|
|
104
|
+
acting: !!grant,
|
|
105
|
+
grant,
|
|
104
106
|
};
|
|
105
107
|
// 4. The identity gate, per the choice.
|
|
106
108
|
if (choice.requireActingIdentity && !(await (0, identity_1.actingIdentitySatisfied)(step, ctx))) {
|
|
@@ -110,13 +112,18 @@ async function executeScanChoice(input, auth) {
|
|
|
110
112
|
// showed. A row that moved on (resolved, claimed away, re-routed) is the
|
|
111
113
|
// same double-actor race a second scan loses.
|
|
112
114
|
const located = await (0, locate_1.locateForStep)(step, ctx, 2);
|
|
113
|
-
|
|
115
|
+
const presented = located?.escalations.find((e) => e.id === input.escalationId);
|
|
116
|
+
if (!presented) {
|
|
114
117
|
return decorate((0, context_1.conflict)('the item is no longer in the presented state'), ctx, input);
|
|
115
118
|
}
|
|
116
119
|
// 6. Dispatch through the same atomic executors a direct scan uses.
|
|
117
120
|
const synthesized = { query: step.query, verb: choice.verb, params: choice.params };
|
|
118
|
-
const result = await (0, verbs_1.dispatchChoiceVerb)(synthesized, ctx,
|
|
119
|
-
|
|
121
|
+
const result = decorate(await (0, verbs_1.dispatchChoiceVerb)(synthesized, ctx, presented), ctx, input);
|
|
122
|
+
// 7. Settle the badge: refund a spend whose act did not land
|
|
123
|
+
const acting = await (0, grant_1.settleGrant)(ctx, result);
|
|
124
|
+
if (acting && result.data)
|
|
125
|
+
result.data = { ...result.data, acting };
|
|
126
|
+
return result;
|
|
120
127
|
}
|
|
121
128
|
catch (err) {
|
|
122
129
|
return { status: 500, error: err.message };
|