@datalayer/core 1.1.60 → 1.1.66
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 -2
- package/lib/api/DatalayerApi.d.ts +42 -1
- package/lib/api/DatalayerApi.js +90 -3
- package/lib/api/constants.d.ts +18 -0
- package/lib/api/constants.js +18 -0
- package/lib/api/contents/attachments.d.ts +9 -0
- package/lib/api/contents/attachments.js +43 -0
- package/lib/api/contents/bridges.d.ts +22 -0
- package/lib/api/contents/bridges.js +79 -0
- package/lib/api/contents/capabilities.d.ts +11 -0
- package/lib/api/contents/capabilities.js +21 -0
- package/lib/api/contents/cloudStorage.d.ts +31 -0
- package/lib/api/contents/cloudStorage.js +89 -0
- package/lib/api/contents/dataservers.d.ts +37 -0
- package/lib/api/contents/dataservers.js +121 -0
- package/lib/api/contents/datasets.d.ts +15 -0
- package/lib/api/contents/datasets.js +45 -0
- package/lib/api/contents/datasources.d.ts +70 -0
- package/lib/api/contents/datasources.js +149 -0
- package/lib/api/contents/generated.d.ts +1070 -0
- package/lib/api/contents/index.d.ts +16 -0
- package/lib/api/contents/index.js +20 -0
- package/lib/api/contents/mcp.d.ts +46 -0
- package/lib/api/contents/mcp.js +157 -0
- package/lib/api/contents/objects.d.ts +31 -0
- package/lib/api/contents/objects.js +88 -0
- package/lib/api/contents/operations.d.ts +3 -0
- package/lib/api/contents/operations.js +19 -0
- package/lib/api/contents/runtimeMounts.d.ts +40 -0
- package/lib/api/contents/runtimeMounts.js +59 -0
- package/lib/api/contents/sandboxUid.d.ts +53 -0
- package/lib/api/contents/sandboxUid.js +78 -0
- package/lib/api/contents/sources.d.ts +19 -0
- package/lib/api/contents/sources.js +119 -0
- package/lib/api/contents/sync.d.ts +18 -0
- package/lib/api/contents/sync.js +69 -0
- package/lib/api/contents/transfers.d.ts +38 -0
- package/lib/api/contents/transfers.js +123 -0
- package/lib/api/iam/connectedAgents.d.ts +39 -0
- package/lib/api/iam/connectedAgents.js +52 -0
- package/lib/api/iam/index.d.ts +2 -1
- package/lib/api/iam/index.js +2 -1
- package/lib/api/iam/mcpAlertRules.d.ts +51 -0
- package/lib/api/iam/mcpAlertRules.js +198 -0
- package/lib/api/iam/mcpAuditSettings.d.ts +46 -0
- package/lib/api/iam/mcpAuditSettings.js +120 -0
- package/lib/api/iam/mcpPolicy.d.ts +80 -0
- package/lib/api/iam/mcpPolicy.js +132 -0
- package/lib/api/iam/organizationTeams.d.ts +13 -0
- package/lib/api/iam/organizationTeams.js +43 -0
- package/lib/api/iam/serviceAgents.d.ts +68 -0
- package/lib/api/iam/serviceAgents.js +119 -0
- package/lib/api/index.d.ts +4 -1
- package/lib/api/index.js +3 -1
- package/lib/api/mcp/activity.d.ts +37 -0
- package/lib/api/mcp/activity.js +26 -0
- package/lib/api/mcp/alerts.d.ts +25 -0
- package/lib/api/mcp/alerts.js +51 -0
- package/lib/api/mcp/audit.d.ts +29 -0
- package/lib/api/mcp/audit.js +55 -0
- package/lib/api/mcp/bindings.d.ts +18 -0
- package/lib/api/mcp/bindings.js +42 -0
- package/lib/api/mcp/forwarding.d.ts +25 -0
- package/lib/api/mcp/forwarding.js +46 -0
- package/lib/api/mcp/gateway.d.ts +16 -0
- package/lib/api/mcp/gateway.js +70 -0
- package/lib/api/mcp/generated.d.ts +22 -0
- package/lib/api/mcp/generated.js +213 -0
- package/lib/api/mcp/index.d.ts +17 -0
- package/lib/api/mcp/index.js +25 -0
- package/lib/api/mcp/observability.d.ts +125 -0
- package/lib/api/mcp/observability.js +254 -0
- package/lib/api/mcp/operations.d.ts +83 -0
- package/lib/api/mcp/operations.js +50 -0
- package/lib/api/mcp/organizations.d.ts +102 -0
- package/lib/api/mcp/organizations.js +46 -0
- package/lib/api/mcp/policy.d.ts +9 -0
- package/lib/api/mcp/policy.js +23 -0
- package/lib/api/mcp/tasks.d.ts +49 -0
- package/lib/api/mcp/tasks.js +147 -0
- package/lib/api/utils/validation.d.ts +1 -1
- package/lib/api/utils/validation.js +1 -1
- package/lib/client/auth/index.js +0 -5
- package/lib/client/auth/storage.js +0 -5
- package/lib/client/auth/strategies.js +0 -5
- package/lib/client/base.d.ts +6 -0
- package/lib/client/base.js +8 -0
- package/lib/client/index.d.ts +0 -10
- package/lib/client/index.js +2 -2
- package/lib/client/mixins/ContentsMixin.d.ts +71 -0
- package/lib/client/mixins/ContentsMixin.js +157 -0
- package/lib/client/mixins/IAMMixin.d.ts +0 -31
- package/lib/client/mixins/IAMMixin.js +0 -58
- package/lib/components/animation/AnimatedText.d.ts +1 -0
- package/lib/components/animation/AnimatedText.js +0 -4
- package/lib/components/auth/SigInAPIKey.d.ts +1 -0
- package/lib/components/auth/SigInAPIKey.js +0 -6
- package/lib/components/auth/SignIn.d.ts +1 -0
- package/lib/components/auth/SignIn.js +0 -4
- package/lib/components/auth/SignIn.stories.d.ts +2 -2
- package/lib/components/avatars/BoringAvatar.d.ts +1 -1
- package/lib/components/avatars/BoringAvatar.stories.d.ts +1 -1
- package/lib/components/avatars/DLAvatar.d.ts +1 -1
- package/lib/components/avatars/DLAvatar.js +0 -4
- package/lib/components/avatars/UserAvatar.d.ts +9 -0
- package/lib/components/avatars/UserAvatar.js +0 -12
- package/lib/components/avatars/UserProfileAvatar.d.ts +1 -1
- package/lib/components/banners/NoAutomationBanner.d.ts +1 -1
- package/lib/components/billing/BillingEntitySelect.d.ts +10 -0
- package/lib/components/billing/BillingEntitySelect.js +0 -17
- package/lib/components/billing/index.js +0 -4
- package/lib/components/buttons/DownloadCSVButton.d.ts +1 -1
- package/lib/components/buttons/DownloadJsonButton.d.ts +1 -1
- package/lib/components/buttons/LongActionButton.d.ts +1 -0
- package/lib/components/buttons/LongActionButton.js +0 -4
- package/lib/components/buttons/UploadButton.d.ts +1 -0
- package/lib/components/buttons/UploadButton.js +0 -4
- package/lib/components/checkout/StripeCheckout.d.ts +1 -1
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +31 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +86 -53
- package/lib/components/collaboration/index.js +0 -4
- package/lib/components/confetti/ConfettiSuccess.d.ts +1 -1
- package/lib/components/contents/ContentSourceCard.d.ts +8 -0
- package/lib/components/contents/ContentSourceCard.js +23 -0
- package/lib/components/contents/HomeFolderObjectsBrowser.d.ts +10 -0
- package/lib/components/contents/HomeFolderObjectsBrowser.js +148 -0
- package/lib/components/contents/index.d.ts +2 -0
- package/lib/components/contents/index.js +6 -0
- package/lib/components/context/OrganizationSelect.d.ts +1 -1
- package/lib/components/display/AvatarSkeleton.d.ts +1 -1
- package/lib/components/display/BusyDots.d.ts +1 -1
- package/lib/components/display/CenteredSpinner.d.ts +1 -1
- package/lib/components/display/CodePreview.d.ts +1 -1
- package/lib/components/display/DatalayerBox.d.ts +7 -2
- package/lib/components/display/DatalayerBox.js +6 -6
- package/lib/components/display/HorizontalCenter.d.ts +1 -1
- package/lib/components/display/JupyterDialog.d.ts +1 -0
- package/lib/components/display/JupyterDialog.js +1 -7
- package/lib/components/display/Markdown.d.ts +1 -0
- package/lib/components/display/NotebookSkeleton.d.ts +1 -1
- package/lib/components/display/Placeholder.d.ts +1 -1
- package/lib/components/display/ToTopBranded.d.ts +1 -1
- package/lib/components/echarts/EChartsReact.d.ts +1 -1
- package/lib/components/flashes/FlashClosable.d.ts +1 -0
- package/lib/components/flashes/FlashClosable.js +0 -4
- package/lib/components/flashes/FlashGuest.d.ts +1 -1
- package/lib/components/flashes/FlashSurveys.d.ts +1 -1
- package/lib/components/flashes/FlashUnauthorized.d.ts +1 -0
- package/lib/components/flashes/FlashUnauthorized.js +0 -6
- package/lib/components/flashes/surveys/Survey2025_1.d.ts +1 -1
- package/lib/components/iam/ExternalTokenSilentLogin.d.ts +1 -1
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +2 -0
- package/lib/components/labels/StatusLabels.d.ts +1 -0
- package/lib/components/labels/VisibilityLabel.d.ts +1 -1
- package/lib/components/landings/StepBlock.d.ts +1 -1
- package/lib/components/mcp/ClientBadge.d.ts +41 -0
- package/lib/components/mcp/ClientBadge.js +33 -0
- package/lib/components/mcp/McpErrorBlankslate.d.ts +21 -0
- package/lib/components/mcp/McpErrorBlankslate.js +17 -0
- package/lib/components/mcp/ScopeList.d.ts +28 -0
- package/lib/components/mcp/ScopeList.js +14 -0
- package/lib/components/mcp/index.d.ts +10 -0
- package/lib/components/mcp/index.js +18 -0
- package/lib/components/navbar/NavigationVisbilityObserver.d.ts +2 -1
- package/lib/components/navbar/SubdomainNavBar.d.ts +10 -5
- package/lib/components/navbar/SubdomainNavBar.js +2 -1
- package/lib/components/notebooks/JupyterNotebook.d.ts +1 -1
- package/lib/components/notebooks/JupyterNotebookToolbar.d.ts +1 -1
- package/lib/components/principal/PrincipalAppearance.d.ts +1 -0
- package/lib/components/principal/PrincipalAppearance.js +0 -8
- package/lib/components/principal/PrincipalAvatar.d.ts +1 -0
- package/lib/components/principal/PrincipalAvatar.js +17 -9
- package/lib/components/principal/PrincipalBadge.d.ts +1 -1
- package/lib/components/principal/PrincipalBanner.d.ts +1 -1
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +31 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +64 -33
- package/lib/components/principal/PrincipalHoverCard.d.ts +31 -0
- package/lib/components/principal/PrincipalHoverCard.js +52 -0
- package/lib/components/principal/PrincipalSwitcherMenu.d.ts +1 -0
- package/lib/components/principal/PrincipalSwitcherMenu.js +5 -14
- package/lib/components/principal/index.d.ts +2 -1
- package/lib/components/principal/index.js +2 -1
- package/lib/components/progress/ConsumptionBar.d.ts +1 -0
- package/lib/components/progress/ConsumptionBar.js +0 -4
- package/lib/components/progress/ProgressRing.d.ts +1 -0
- package/lib/components/progress/SpinnerBar.d.ts +1 -1
- package/lib/components/progress/Timer.d.ts +1 -0
- package/lib/components/progress/Timer.js +0 -4
- package/lib/components/screencapture/ScreencaptureButton.d.ts +1 -1
- package/lib/components/sharing/ShareAccessComponent.d.ts +15 -1
- package/lib/components/sharing/ShareAccessComponent.js +36 -23
- package/lib/components/sharing/SharingEditor.d.ts +17 -0
- package/lib/components/sharing/SharingEditor.js +0 -24
- package/lib/components/sparklines/Sparklines.js +3 -4
- package/lib/components/sparklines/index.js +0 -4
- package/lib/components/storage/ContentsBrowser.d.ts +25 -1
- package/lib/components/storage/ContentsBrowser.js +145 -52
- package/lib/components/storage/ContentsBrowserMock.d.ts +3 -0
- package/lib/components/storage/ContentsBrowserMock.js +75 -0
- package/lib/components/storage/ContentsItems.d.ts +2 -1
- package/lib/components/storage/ContentsItems.js +0 -4
- package/lib/components/storage/ContentsTable.d.ts +50 -0
- package/lib/components/storage/ContentsTable.js +134 -0
- package/lib/components/storage/HomeFolderDrive.d.ts +71 -0
- package/lib/components/storage/HomeFolderDrive.js +248 -0
- package/lib/components/storage/index.d.ts +2 -0
- package/lib/components/storage/index.js +2 -0
- package/lib/components/students/StudentItemStatus.d.ts +1 -1
- package/lib/components/students/StudentResultCircle.d.ts +1 -1
- package/lib/components/subnav/SubNav.d.ts +5 -5
- package/lib/components/subnav/SubNav.js +11 -9
- package/lib/components/tables/DataTable.d.ts +1 -1
- package/lib/components/text-reveal/TextRevealAnimation.d.ts +2 -2
- package/lib/components/text-reveal/useTextRevealAnimationLines.d.ts +1 -1
- package/lib/components/time/LiveRelativeTime.d.ts +1 -0
- package/lib/components/time/LiveRelativeTime.js +0 -8
- package/lib/components/time/index.js +0 -4
- package/lib/components/timeline/Timeline.d.ts +1 -1
- package/lib/components/timeline/index.js +0 -4
- package/lib/components/users/PeerIndicator.d.ts +1 -0
- package/lib/components/users/PeerIndicator.js +0 -4
- package/lib/config/Configuration.d.ts +15 -17
- package/lib/config/Configuration.js +7 -0
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/index.js +2 -0
- package/lib/hooks/layouts/LayoutBackdrop.d.ts +1 -1
- package/lib/hooks/layouts/LayoutScreencapture.d.ts +1 -1
- package/lib/hooks/useBackdrop.d.ts +1 -1
- package/lib/hooks/useBackdropJupyterLab.d.ts +3 -2
- package/lib/hooks/useBackdropJupyterLab.js +0 -4
- package/lib/hooks/useCache.d.ts +310 -105
- package/lib/hooks/useCache.js +142 -355
- package/lib/hooks/useContainsFocus.d.ts +1 -1
- package/lib/hooks/useContents.d.ts +313 -0
- package/lib/hooks/useContents.js +964 -0
- package/lib/hooks/useFocusTrap.d.ts +4 -4
- package/lib/hooks/useFocusTrap.js +1 -1
- package/lib/hooks/useMcp.d.ts +229 -0
- package/lib/hooks/useMcp.js +656 -0
- package/lib/hooks/useMobile.js +0 -5
- package/lib/hooks/useProjectStore.d.ts +3 -3
- package/lib/hooks/useProjectStore.js +4 -4
- package/lib/hooks/useRef.d.ts +1 -1
- package/lib/hooks/useRef.js +3 -0
- package/lib/hooks/useScreencapture.d.ts +2 -1
- package/lib/hooks/useScreencapture.js +0 -4
- package/lib/hooks/useToast.js +0 -4
- package/lib/hooks/useVisibilityObserver.d.ts +1 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +3 -1
- package/lib/mocks/components/FlashMock.d.ts +1 -1
- package/lib/mocks/views/ActionMenuMock.d.ts +1 -1
- package/lib/mocks/views/ChartMock.d.ts +1 -1
- package/lib/mocks/views/DashboardMock.d.ts +1 -1
- package/lib/mocks/views/FormMock.d.ts +1 -1
- package/lib/mocks/views/TableMock.d.ts +1 -1
- package/lib/mocks/views/WipMock.d.ts +1 -1
- package/lib/models/Credits.d.ts +16 -0
- package/lib/models/Credits.js +22 -0
- package/lib/models/McpAuditEvent.d.ts +52 -0
- package/lib/models/McpAuditEvent.js +5 -0
- package/lib/models/McpBinding.d.ts +51 -0
- package/lib/models/McpBinding.js +5 -0
- package/lib/models/McpPolicy.d.ts +77 -0
- package/lib/models/McpPolicy.js +5 -0
- package/lib/models/McpTask.d.ts +94 -0
- package/lib/models/McpTask.js +7 -0
- package/lib/models/StartedBy.d.ts +57 -0
- package/lib/models/StartedBy.js +81 -0
- package/lib/models/UserDTO.js +4 -2
- package/lib/models/contents/__fixtures__/solr-codec.json +57 -0
- package/lib/models/contents/__fixtures__/v1-contracts.json +66 -0
- package/lib/models/contents/fieldCase.d.ts +8 -0
- package/lib/models/contents/fieldCase.js +37 -0
- package/lib/models/contents/index.d.ts +2 -0
- package/lib/models/contents/index.js +6 -0
- package/lib/models/contents/permissions.d.ts +16 -0
- package/lib/models/contents/permissions.js +34 -0
- package/lib/models/index.d.ts +5 -1
- package/lib/models/index.js +5 -1
- package/lib/state/substates/CoreState.d.ts +15 -7
- package/lib/state/substates/CoreState.js +18 -14
- package/lib/state/substates/IAMState.d.ts +1 -1
- package/lib/state/substates/IAMState.js +5 -5
- package/lib/state/substates/ProfileState.js +0 -4
- package/lib/state/substates/index.d.ts +0 -1
- package/lib/state/substates/index.js +0 -1
- package/lib/utils/Jwt.d.ts +26 -0
- package/lib/utils/Jwt.js +37 -0
- package/lib/utils/WithSuspense.d.ts +1 -1
- package/lib/views/api-keys/APIKeyEdit.d.ts +1 -1
- package/lib/views/api-keys/APIKeyNew.d.ts +1 -1
- package/lib/views/api-keys/APIKeys.d.ts +1 -1
- package/lib/views/api-keys/APIKeys.js +30 -4
- package/lib/views/api-keys/APIKeysStandalone.d.ts +5 -1
- package/lib/views/api-keys/APIKeysStandalone.js +2 -2
- package/lib/views/datasources/DatasourceDetail.d.ts +12 -5
- package/lib/views/datasources/DatasourceDetail.js +80 -110
- package/lib/views/datasources/DatasourceNew.d.ts +14 -8
- package/lib/views/datasources/DatasourceNew.js +129 -118
- package/lib/views/datasources/Datasources.d.ts +39 -7
- package/lib/views/datasources/Datasources.js +95 -40
- package/lib/views/datasources/DatasourcesMock.d.ts +3 -0
- package/lib/views/datasources/DatasourcesMock.js +57 -0
- package/lib/views/iam/SignInSimple.d.ts +12 -0
- package/lib/views/iam/SignInSimple.js +4 -4
- package/lib/views/index.d.ts +1 -0
- package/lib/views/index.js +1 -0
- package/lib/views/mcp/AlertDestinations.d.ts +34 -0
- package/lib/views/mcp/AlertDestinations.js +75 -0
- package/lib/views/mcp/AlertRules.d.ts +32 -0
- package/lib/views/mcp/AlertRules.js +245 -0
- package/lib/views/mcp/AuditLog.d.ts +42 -0
- package/lib/views/mcp/AuditLog.js +144 -0
- package/lib/views/mcp/ConnectedAgents.d.ts +34 -0
- package/lib/views/mcp/ConnectedAgents.js +100 -0
- package/lib/views/mcp/EnterpriseConsole.d.ts +74 -0
- package/lib/views/mcp/EnterpriseConsole.js +173 -0
- package/lib/views/mcp/McpDashboard.d.ts +50 -0
- package/lib/views/mcp/McpDashboard.js +347 -0
- package/lib/views/mcp/McpHome.d.ts +92 -0
- package/lib/views/mcp/McpHome.js +153 -0
- package/lib/views/mcp/McpObservability.d.ts +50 -0
- package/lib/views/mcp/McpObservability.js +150 -0
- package/lib/views/mcp/OrganizationPolicy.d.ts +45 -0
- package/lib/views/mcp/OrganizationPolicy.js +107 -0
- package/lib/views/mcp/OrganizationUsage.d.ts +69 -0
- package/lib/views/mcp/OrganizationUsage.js +113 -0
- package/lib/views/mcp/PersonalPolicy.d.ts +54 -0
- package/lib/views/mcp/PersonalPolicy.js +125 -0
- package/lib/views/mcp/Policies.d.ts +53 -0
- package/lib/views/mcp/Policies.js +210 -0
- package/lib/views/mcp/PolicyForm.d.ts +67 -0
- package/lib/views/mcp/PolicyForm.js +91 -0
- package/lib/views/mcp/PolicyHistory.d.ts +45 -0
- package/lib/views/mcp/PolicyHistory.js +62 -0
- package/lib/views/mcp/ServiceAgents.d.ts +46 -0
- package/lib/views/mcp/ServiceAgents.js +194 -0
- package/lib/views/mcp/TeamPolicies.d.ts +33 -0
- package/lib/views/mcp/TeamPolicies.js +98 -0
- package/lib/views/mcp/format.d.ts +25 -0
- package/lib/views/mcp/format.js +77 -0
- package/lib/views/mcp/index.d.ts +29 -0
- package/lib/views/mcp/index.js +37 -0
- package/lib/views/mcp/types.d.ts +66 -0
- package/lib/views/mcp/types.js +5 -0
- package/lib/views/secrets/SecretEdit.d.ts +5 -1
- package/lib/views/secrets/SecretEdit.js +2 -2
- package/lib/views/secrets/SecretNew.d.ts +11 -1
- package/lib/views/secrets/SecretNew.js +2 -2
- package/lib/views/secrets/Secrets.d.ts +12 -1
- package/lib/views/secrets/Secrets.js +2 -2
- package/package.json +28 -24
- package/scripts/generate-contents-types.py +169 -0
- package/scripts/generate-mcp-types.py +233 -0
- package/lib/api/iam/datasources.d.ts +0 -86
- package/lib/api/iam/datasources.js +0 -185
- package/lib/components/toolbars/index.d.ts +0 -1
- package/lib/models/Datasource.d.ts +0 -181
- package/lib/models/Datasource.js +0 -154
- package/lib/state/substates/DatasourceState.d.ts +0 -11
- package/lib/state/substates/DatasourceState.js +0 -16
- /package/lib/{components/toolbars/index.js → api/contents/generated.js} +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/*
|
|
6
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
7
|
+
* Distributed under the terms of the Modified BSD License.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The alert rules an organization asked to be told about.
|
|
11
|
+
*
|
|
12
|
+
* A rule is a condition over a number the gateway already counts, checked
|
|
13
|
+
* once a minute, firing at most once per condition per window. IAM stores
|
|
14
|
+
* them; the gateway's evaluator reads the enabled ones on its tick.
|
|
15
|
+
*
|
|
16
|
+
* **A rule the evaluator cannot evaluate is refused when it is written**,
|
|
17
|
+
* not at the tick. That is the whole reason the vocabulary below is
|
|
18
|
+
* exported: a rule that never fires because of a typo is indistinguishable
|
|
19
|
+
* from a condition that never happened, and the second is what somebody will
|
|
20
|
+
* believe. A form built from these lists cannot produce one.
|
|
21
|
+
*
|
|
22
|
+
* @module api/iam/mcpAlertRules
|
|
23
|
+
*/
|
|
24
|
+
import { requestDatalayerAPI } from '../DatalayerApi.js';
|
|
25
|
+
import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants.js';
|
|
26
|
+
/**
|
|
27
|
+
* What a rule may be about.
|
|
28
|
+
*
|
|
29
|
+
* Each name is answered by one reader in the gateway. `measurable` says
|
|
30
|
+
* whether a reader exists *today*: a condition with none is counted
|
|
31
|
+
* unreadable and never fires, which is exactly the silence rules are written
|
|
32
|
+
* to avoid — so a form shows it as not yet available rather than offering it
|
|
33
|
+
* as though it worked.
|
|
34
|
+
*/
|
|
35
|
+
export const ALERT_CONDITIONS = [
|
|
36
|
+
{
|
|
37
|
+
name: 'tasks.open',
|
|
38
|
+
label: 'Open tasks',
|
|
39
|
+
help: 'Runs still going. A backlog that is not draining.',
|
|
40
|
+
measurable: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'tasks.failed',
|
|
44
|
+
label: 'Failed tasks',
|
|
45
|
+
help: 'Runs that ended badly in the window.',
|
|
46
|
+
measurable: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'spend.budget_fraction',
|
|
50
|
+
label: 'Share of the daily budget spent',
|
|
51
|
+
help: 'A fraction: 0.8 is 80%. Prefer this to a credit figure — it follows ' +
|
|
52
|
+
'the budget, so raising the budget does not silently move the alert.',
|
|
53
|
+
measurable: true,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'spend.credits',
|
|
57
|
+
label: 'Credits spent',
|
|
58
|
+
help: 'An absolute figure. Raising the budget leaves this rule where it ' +
|
|
59
|
+
'was, and you will be told at a share you did not choose.',
|
|
60
|
+
measurable: true,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'audit.write_failures',
|
|
64
|
+
label: 'Audit writes that failed',
|
|
65
|
+
help: 'A gap in the record. Worth knowing about immediately.',
|
|
66
|
+
measurable: true,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'sli.availability',
|
|
70
|
+
label: 'Availability',
|
|
71
|
+
help: 'The share of calls that did not fail, as a fraction. An organization ' +
|
|
72
|
+
'with no calls has no availability, and is not alerted on.',
|
|
73
|
+
measurable: true,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'sli.latency',
|
|
77
|
+
label: 'Latency (p95)',
|
|
78
|
+
help: 'The 95th percentile call duration, in milliseconds, read from the ' +
|
|
79
|
+
'audit — which is never sampled, so it is the more complete source. ' +
|
|
80
|
+
'A very busy window holds more rows than one reading takes, and is ' +
|
|
81
|
+
'reported as unreadable rather than as a percentile of the newest ' +
|
|
82
|
+
'slice; Test says so straight away.',
|
|
83
|
+
measurable: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'sandbox.lost',
|
|
87
|
+
label: 'Sandboxes lost',
|
|
88
|
+
help: 'Sessions whose sandbox was found gone. Relaunched ones count: a ' +
|
|
89
|
+
'provider losing sandboxes steadily and quietly relaunching them is ' +
|
|
90
|
+
'invisible from the outside precisely because it recovers.',
|
|
91
|
+
measurable: true,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'dependency.down',
|
|
95
|
+
label: 'Dependencies down',
|
|
96
|
+
help: 'How many of the services the gateway needs are not answering. A ' +
|
|
97
|
+
'service nobody configured is not counted. This is about the ' +
|
|
98
|
+
'platform rather than your organization, and is your outage anyway.',
|
|
99
|
+
measurable: true,
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
/** How a reading is compared with the threshold. */
|
|
103
|
+
export const ALERT_OPERATORS = [
|
|
104
|
+
{ name: 'gt', label: 'is above' },
|
|
105
|
+
{ name: 'gte', label: 'is at or above' },
|
|
106
|
+
{ name: 'lt', label: 'is below' },
|
|
107
|
+
{ name: 'lte', label: 'is at or below' },
|
|
108
|
+
{ name: 'eq', label: 'equals' },
|
|
109
|
+
];
|
|
110
|
+
export const ALERT_SEVERITIES = ['info', 'warning', 'critical'];
|
|
111
|
+
/** What a rule is measured over. */
|
|
112
|
+
export const ALERT_SCOPE_KINDS = ['organization', 'team', 'user', 'agent'];
|
|
113
|
+
const fromWire = (rule) => ({
|
|
114
|
+
uid: rule.uid,
|
|
115
|
+
orgUid: rule.org_uid ?? '',
|
|
116
|
+
condition: rule.condition ?? '',
|
|
117
|
+
operator: rule.operator ?? 'gt',
|
|
118
|
+
threshold: rule.threshold ?? 0,
|
|
119
|
+
severity: rule.severity ?? 'warning',
|
|
120
|
+
windowSeconds: rule.window_seconds ?? 3600,
|
|
121
|
+
scopeKind: rule.scope_kind ?? 'organization',
|
|
122
|
+
scopeUid: rule.scope_uid ?? '',
|
|
123
|
+
enabled: rule.enabled ?? true,
|
|
124
|
+
version: rule.version ?? undefined,
|
|
125
|
+
});
|
|
126
|
+
const toWire = (rule) => ({
|
|
127
|
+
condition: rule.condition,
|
|
128
|
+
operator: rule.operator,
|
|
129
|
+
threshold: rule.threshold,
|
|
130
|
+
severity: rule.severity,
|
|
131
|
+
window_seconds: rule.windowSeconds,
|
|
132
|
+
scope_kind: rule.scopeKind,
|
|
133
|
+
scope_uid: rule.scopeUid,
|
|
134
|
+
enabled: rule.enabled,
|
|
135
|
+
});
|
|
136
|
+
/** Raised when the evaluator could not evaluate the rule as written. */
|
|
137
|
+
export class AlertRuleInvalid extends Error {
|
|
138
|
+
constructor(message) {
|
|
139
|
+
super(message);
|
|
140
|
+
this.name = 'AlertRuleInvalid';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const rulesUrl = (baseUrl, orgUid, suffix = '') => `${baseUrl}${API_BASE_PATHS.IAM}/mcp-alert-rules/${encodeURIComponent(orgUid)}${suffix}`;
|
|
144
|
+
export const listAlertRules = async (token, orgUid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
145
|
+
const response = await requestDatalayerAPI({
|
|
146
|
+
url: rulesUrl(baseUrl, orgUid),
|
|
147
|
+
method: 'GET',
|
|
148
|
+
token,
|
|
149
|
+
});
|
|
150
|
+
return (response.rules ?? []).map(fromWire);
|
|
151
|
+
};
|
|
152
|
+
export const createAlertRule = async (token, orgUid, rule, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
153
|
+
const response = await withRefusal(() => requestDatalayerAPI({
|
|
154
|
+
url: rulesUrl(baseUrl, orgUid),
|
|
155
|
+
method: 'POST',
|
|
156
|
+
token,
|
|
157
|
+
body: toWire(rule),
|
|
158
|
+
}));
|
|
159
|
+
return fromWire(response.rule ?? { uid: '' });
|
|
160
|
+
};
|
|
161
|
+
export const updateAlertRule = async (token, orgUid, uid, rule, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
162
|
+
const response = await withRefusal(() => requestDatalayerAPI({
|
|
163
|
+
url: rulesUrl(baseUrl, orgUid, `/${encodeURIComponent(uid)}`),
|
|
164
|
+
method: 'PUT',
|
|
165
|
+
token,
|
|
166
|
+
body: toWire(rule),
|
|
167
|
+
}));
|
|
168
|
+
return fromWire(response.rule ?? { uid });
|
|
169
|
+
};
|
|
170
|
+
/** Remove one rule. What it watched is unwatched from the next tick. */
|
|
171
|
+
export const deleteAlertRule = async (token, orgUid, uid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
172
|
+
await requestDatalayerAPI({
|
|
173
|
+
url: rulesUrl(baseUrl, orgUid, `/${encodeURIComponent(uid)}`),
|
|
174
|
+
method: 'DELETE',
|
|
175
|
+
token,
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Turn IAM's refusal into one the form can show beside the field.
|
|
180
|
+
*
|
|
181
|
+
* `422` here is never a bug in the client: it is the evaluator saying it
|
|
182
|
+
* could not evaluate this rule, and its message names what is wrong. Losing
|
|
183
|
+
* it behind a generic failure would leave somebody guessing at the one thing
|
|
184
|
+
* the platform already knows.
|
|
185
|
+
*/
|
|
186
|
+
const withRefusal = async (call) => {
|
|
187
|
+
try {
|
|
188
|
+
return await call();
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
const candidate = error;
|
|
192
|
+
const status = candidate?.status ?? candidate?.response?.status;
|
|
193
|
+
if (status === 422) {
|
|
194
|
+
throw new AlertRuleInvalid(candidate.message || 'The evaluator could not evaluate this rule.');
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Where a copy of the audit can be shipped. */
|
|
2
|
+
export declare const AUDIT_DESTINATION_KINDS: readonly ["https", "s3"];
|
|
3
|
+
export type AuditDestinationKind = (typeof AUDIT_DESTINATION_KINDS)[number];
|
|
4
|
+
export interface McpAuditSettings {
|
|
5
|
+
/** How long rows are kept. Rows a sweep deletes do not come back. */
|
|
6
|
+
retentionDays?: number;
|
|
7
|
+
/** Empty when nothing is forwarded, which is the default. */
|
|
8
|
+
destinationKind: string;
|
|
9
|
+
destinationUrl: string;
|
|
10
|
+
/** The *reference* to a signing secret, never the secret. */
|
|
11
|
+
destinationSecretRef: string;
|
|
12
|
+
/** An endpoint a fired alert is POSTed to, beside the in-app notice. */
|
|
13
|
+
alertWebhookUrl: string;
|
|
14
|
+
/** A Slack incoming webhook. Its own field: Slack gets Block Kit. */
|
|
15
|
+
alertSlackWebhookUrl: string;
|
|
16
|
+
/** Who is emailed, comma-separated. */
|
|
17
|
+
alertEmails: string;
|
|
18
|
+
version?: number;
|
|
19
|
+
}
|
|
20
|
+
/** Raised when IAM refused a setting, carrying its reason. */
|
|
21
|
+
export declare class AuditSettingInvalid extends Error {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One organization's settings, or `null` where it has none.
|
|
26
|
+
*
|
|
27
|
+
* `null` is an ordinary answer: an organization with no settings is neither
|
|
28
|
+
* swept nor forwarded, which is the default rather than a failure.
|
|
29
|
+
*/
|
|
30
|
+
export declare const getAuditSettings: (token: string, orgUid: string, baseUrl?: string) => Promise<McpAuditSettings | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Change some of the settings. Only what is passed is sent.
|
|
33
|
+
*
|
|
34
|
+
* A partial write, deliberately: IAM merges, so a page that only edits the
|
|
35
|
+
* alert destinations must not send an empty retention and clear it.
|
|
36
|
+
*/
|
|
37
|
+
export declare const setAuditSettings: (token: string, orgUid: string, settings: Partial<{
|
|
38
|
+
retentionDays: number;
|
|
39
|
+
destinationKind: string;
|
|
40
|
+
destinationUrl: string;
|
|
41
|
+
alertWebhookUrl: string;
|
|
42
|
+
alertSlackWebhookUrl: string;
|
|
43
|
+
alertEmails: string;
|
|
44
|
+
}>, options?: {
|
|
45
|
+
expectedVersion?: number;
|
|
46
|
+
}, baseUrl?: string) => Promise<McpAuditSettings>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/*
|
|
6
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
7
|
+
* Distributed under the terms of the Modified BSD License.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* An organization's audit retention, forwarding, and where alerts go.
|
|
11
|
+
*
|
|
12
|
+
* Three things on one document, and they are together because they are all
|
|
13
|
+
* "what this organization has decided about its own record": how long rows
|
|
14
|
+
* are kept, where a copy is shipped, and who is told when a rule fires.
|
|
15
|
+
*
|
|
16
|
+
* The alert destinations live here rather than on each rule because a URL
|
|
17
|
+
* repeated on twenty rules is nineteen places to forget when it rotates,
|
|
18
|
+
* and the rules would disagree about where the organization is reachable.
|
|
19
|
+
*
|
|
20
|
+
* **Writes merge, they do not replace.** Retention and forwarding are set by
|
|
21
|
+
* different people at different times, and making somebody resend a
|
|
22
|
+
* destination in order to change a retention is how a destination gets
|
|
23
|
+
* resent wrong.
|
|
24
|
+
*
|
|
25
|
+
* @module api/iam/mcpAuditSettings
|
|
26
|
+
*/
|
|
27
|
+
import { requestDatalayerAPI } from '../DatalayerApi.js';
|
|
28
|
+
import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants.js';
|
|
29
|
+
/** Where a copy of the audit can be shipped. */
|
|
30
|
+
export const AUDIT_DESTINATION_KINDS = ['https', 's3'];
|
|
31
|
+
const fromWire = (settings) => ({
|
|
32
|
+
retentionDays: settings.retention_days ?? undefined,
|
|
33
|
+
destinationKind: settings.destination_kind ?? '',
|
|
34
|
+
destinationUrl: settings.destination_url ?? '',
|
|
35
|
+
destinationSecretRef: settings.destination_secret_ref ?? '',
|
|
36
|
+
alertWebhookUrl: settings.alert_webhook_url ?? '',
|
|
37
|
+
alertSlackWebhookUrl: settings.alert_slack_webhook_url ?? '',
|
|
38
|
+
alertEmails: settings.alert_emails ?? '',
|
|
39
|
+
version: settings.version ?? undefined,
|
|
40
|
+
});
|
|
41
|
+
/** Raised when IAM refused a setting, carrying its reason. */
|
|
42
|
+
export class AuditSettingInvalid extends Error {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'AuditSettingInvalid';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const settingsUrl = (baseUrl, orgUid, query = '') => `${baseUrl}${API_BASE_PATHS.IAM}/mcp-audit-settings/${encodeURIComponent(orgUid)}${query}`;
|
|
49
|
+
/**
|
|
50
|
+
* One organization's settings, or `null` where it has none.
|
|
51
|
+
*
|
|
52
|
+
* `null` is an ordinary answer: an organization with no settings is neither
|
|
53
|
+
* swept nor forwarded, which is the default rather than a failure.
|
|
54
|
+
*/
|
|
55
|
+
export const getAuditSettings = async (token, orgUid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
56
|
+
try {
|
|
57
|
+
const response = await requestDatalayerAPI({
|
|
58
|
+
url: settingsUrl(baseUrl, orgUid),
|
|
59
|
+
method: 'GET',
|
|
60
|
+
token,
|
|
61
|
+
});
|
|
62
|
+
return fromWire(response.settings ?? {});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (statusOf(error) === 404) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Change some of the settings. Only what is passed is sent.
|
|
73
|
+
*
|
|
74
|
+
* A partial write, deliberately: IAM merges, so a page that only edits the
|
|
75
|
+
* alert destinations must not send an empty retention and clear it.
|
|
76
|
+
*/
|
|
77
|
+
export const setAuditSettings = async (token, orgUid, settings, options = {}, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
78
|
+
const body = {};
|
|
79
|
+
if (settings.retentionDays !== undefined) {
|
|
80
|
+
body.retention_days = settings.retentionDays;
|
|
81
|
+
}
|
|
82
|
+
if (settings.destinationKind !== undefined) {
|
|
83
|
+
body.destination_kind = settings.destinationKind;
|
|
84
|
+
}
|
|
85
|
+
if (settings.destinationUrl !== undefined) {
|
|
86
|
+
body.destination_url = settings.destinationUrl;
|
|
87
|
+
}
|
|
88
|
+
if (settings.alertWebhookUrl !== undefined) {
|
|
89
|
+
body.alert_webhook_url = settings.alertWebhookUrl;
|
|
90
|
+
}
|
|
91
|
+
if (settings.alertSlackWebhookUrl !== undefined) {
|
|
92
|
+
body.alert_slack_webhook_url = settings.alertSlackWebhookUrl;
|
|
93
|
+
}
|
|
94
|
+
if (settings.alertEmails !== undefined) {
|
|
95
|
+
body.alert_emails = settings.alertEmails;
|
|
96
|
+
}
|
|
97
|
+
const query = options.expectedVersion === undefined
|
|
98
|
+
? ''
|
|
99
|
+
: `?expected_version=${encodeURIComponent(String(options.expectedVersion))}`;
|
|
100
|
+
try {
|
|
101
|
+
const response = await requestDatalayerAPI({
|
|
102
|
+
url: settingsUrl(baseUrl, orgUid, query),
|
|
103
|
+
method: 'PUT',
|
|
104
|
+
token,
|
|
105
|
+
body,
|
|
106
|
+
});
|
|
107
|
+
return fromWire(response.settings ?? {});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (statusOf(error) === 422) {
|
|
111
|
+
throw new AuditSettingInvalid(error.message ||
|
|
112
|
+
'IAM refused one of these settings.');
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const statusOf = (error) => {
|
|
118
|
+
const candidate = error;
|
|
119
|
+
return candidate?.status ?? candidate?.response?.status;
|
|
120
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which layer a policy belongs to.
|
|
3
|
+
*
|
|
4
|
+
* The names IAM stores, exactly: `personal`, not `user`. It refuses an
|
|
5
|
+
* unknown scope with a `422`, so getting this wrong is not a subtle
|
|
6
|
+
* mis-scoping — it is every read and every write of that layer failing.
|
|
7
|
+
* Held against IAM's own `POLICY_SCOPES` by a test in the gateway's suite.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MCP_POLICY_SCOPES: readonly ["organization", "team", "personal"];
|
|
10
|
+
export type McpPolicyScope = (typeof MCP_POLICY_SCOPES)[number];
|
|
11
|
+
/**
|
|
12
|
+
* The rules a layer may set. Every one is optional: a layer that sets
|
|
13
|
+
* nothing narrows nothing.
|
|
14
|
+
*/
|
|
15
|
+
export interface McpPolicyRules {
|
|
16
|
+
/** Tools this layer forbids. Unions across layers — a layer may add a
|
|
17
|
+
* denial, never lift one. */
|
|
18
|
+
toolDenylist?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Tools this layer permits, to the exclusion of the rest.
|
|
21
|
+
*
|
|
22
|
+
* An empty array **as written** is not an allowlist. A setting somebody
|
|
23
|
+
* created and has not filled in would otherwise refuse every tool in the
|
|
24
|
+
* organization, and the first conclusion drawn would be that the gateway
|
|
25
|
+
* is down.
|
|
26
|
+
*/
|
|
27
|
+
toolAllowlist?: string[];
|
|
28
|
+
/** CIMD URLs or hostnames this layer admits. Empty is not an allowlist,
|
|
29
|
+
* for the same reason. */
|
|
30
|
+
allowedClients?: string[];
|
|
31
|
+
/** Calls a minute. **At least 1**: see `maxCallsPerMinute` below. */
|
|
32
|
+
maxCallsPerMinute?: number;
|
|
33
|
+
/** Credits a day, across the layer's agents. */
|
|
34
|
+
maxCreditsPerDay?: number;
|
|
35
|
+
/** Sandboxes at once. Counted per scope — a team's counts the team's. */
|
|
36
|
+
maxConcurrentSandboxes?: number;
|
|
37
|
+
}
|
|
38
|
+
/** A stored layer, with the version a write must carry back. */
|
|
39
|
+
export interface McpPolicy extends McpPolicyRules {
|
|
40
|
+
/**
|
|
41
|
+
* What was read. Passed to the next write so a change made in between is
|
|
42
|
+
* a `409` rather than a silent overwrite.
|
|
43
|
+
*
|
|
44
|
+
* Absent when nobody has written this layer yet.
|
|
45
|
+
*/
|
|
46
|
+
version?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Every rule name IAM will store, in the order a form should show them.
|
|
50
|
+
*
|
|
51
|
+
* Exported so a form cannot drift from what the gateway enforces: a rule
|
|
52
|
+
* rendered here and unknown to IAM is refused at the write, which reads to
|
|
53
|
+
* the person filling it in as the page being broken.
|
|
54
|
+
*/
|
|
55
|
+
export declare const MCP_POLICY_RULES: readonly ["toolDenylist", "toolAllowlist", "allowedClients", "maxCallsPerMinute", "maxCreditsPerDay", "maxConcurrentSandboxes"];
|
|
56
|
+
/** Raised when the policy changed between the read and the write. */
|
|
57
|
+
export declare class McpPolicyConflict extends Error {
|
|
58
|
+
constructor(message: string);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One layer's rules, or `null` where nobody has written it.
|
|
62
|
+
*
|
|
63
|
+
* `null` rather than an empty object, and the difference is the whole point:
|
|
64
|
+
* "this organization narrows nothing" and "this organization has no policy"
|
|
65
|
+
* are the same in effect and different to edit. A form that cannot tell them
|
|
66
|
+
* apart offers **Remove** for a policy that does not exist.
|
|
67
|
+
*/
|
|
68
|
+
export declare const getMcpPolicy: (token: string, scope: McpPolicyScope, subjectUid: string, baseUrl?: string) => Promise<McpPolicy | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Replace one layer's rules.
|
|
71
|
+
*
|
|
72
|
+
* Replace, not merge: a policy is read whole and small, and merging would
|
|
73
|
+
* leave no way to express *removing* a rule — an owner clearing a denylist
|
|
74
|
+
* would find it still there.
|
|
75
|
+
*/
|
|
76
|
+
export declare const setMcpPolicy: (token: string, scope: McpPolicyScope, subjectUid: string, rules: McpPolicyRules, options?: {
|
|
77
|
+
expectedVersion?: number;
|
|
78
|
+
}, baseUrl?: string) => Promise<McpPolicy>;
|
|
79
|
+
/** Remove one layer, so it narrows nothing again. */
|
|
80
|
+
export declare const deleteMcpPolicy: (token: string, scope: McpPolicyScope, subjectUid: string, baseUrl?: string) => Promise<void>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/*
|
|
6
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
7
|
+
* Distributed under the terms of the Modified BSD License.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The MCP policy layer IAM holds for one organization, team or person.
|
|
11
|
+
*
|
|
12
|
+
* Distinct from the *effective* policy the gateway answers, which is every
|
|
13
|
+
* layer intersected with, per rule, the layer that decided it. That one is
|
|
14
|
+
* the read: "what may my agent do, and who said so". This one is the write:
|
|
15
|
+
* "what does my organization narrow".
|
|
16
|
+
*
|
|
17
|
+
* Only four kinds of rule can be stored, and IAM refuses anything else —
|
|
18
|
+
* so a policy page cannot promise something the gateway never enforces, and
|
|
19
|
+
* `toolDenyList` comes back as a typo rather than being kept as a setting
|
|
20
|
+
* that does nothing.
|
|
21
|
+
*
|
|
22
|
+
* **Writes carry the version that was read.** A policy is small and read
|
|
23
|
+
* whole, so a write replaces rather than merges; without the version, two
|
|
24
|
+
* owners editing the same policy would each silently overwrite the other,
|
|
25
|
+
* and the loser would not know. IAM answers `409` instead, and the caller
|
|
26
|
+
* re-reads.
|
|
27
|
+
*
|
|
28
|
+
* @module api/iam/mcpPolicy
|
|
29
|
+
*/
|
|
30
|
+
import { requestDatalayerAPI } from '../DatalayerApi.js';
|
|
31
|
+
import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants.js';
|
|
32
|
+
/**
|
|
33
|
+
* Which layer a policy belongs to.
|
|
34
|
+
*
|
|
35
|
+
* The names IAM stores, exactly: `personal`, not `user`. It refuses an
|
|
36
|
+
* unknown scope with a `422`, so getting this wrong is not a subtle
|
|
37
|
+
* mis-scoping — it is every read and every write of that layer failing.
|
|
38
|
+
* Held against IAM's own `POLICY_SCOPES` by a test in the gateway's suite.
|
|
39
|
+
*/
|
|
40
|
+
export const MCP_POLICY_SCOPES = ['organization', 'team', 'personal'];
|
|
41
|
+
/**
|
|
42
|
+
* Every rule name IAM will store, in the order a form should show them.
|
|
43
|
+
*
|
|
44
|
+
* Exported so a form cannot drift from what the gateway enforces: a rule
|
|
45
|
+
* rendered here and unknown to IAM is refused at the write, which reads to
|
|
46
|
+
* the person filling it in as the page being broken.
|
|
47
|
+
*/
|
|
48
|
+
export const MCP_POLICY_RULES = [
|
|
49
|
+
'toolDenylist',
|
|
50
|
+
'toolAllowlist',
|
|
51
|
+
'allowedClients',
|
|
52
|
+
'maxCallsPerMinute',
|
|
53
|
+
'maxCreditsPerDay',
|
|
54
|
+
'maxConcurrentSandboxes',
|
|
55
|
+
];
|
|
56
|
+
/** Raised when the policy changed between the read and the write. */
|
|
57
|
+
export class McpPolicyConflict extends Error {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = 'McpPolicyConflict';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const policyUrl = (baseUrl, scope, subjectUid, query = '') => `${baseUrl}${API_BASE_PATHS.IAM}/mcp-policies/${encodeURIComponent(scope)}` +
|
|
64
|
+
`/${encodeURIComponent(subjectUid)}${query}`;
|
|
65
|
+
/**
|
|
66
|
+
* One layer's rules, or `null` where nobody has written it.
|
|
67
|
+
*
|
|
68
|
+
* `null` rather than an empty object, and the difference is the whole point:
|
|
69
|
+
* "this organization narrows nothing" and "this organization has no policy"
|
|
70
|
+
* are the same in effect and different to edit. A form that cannot tell them
|
|
71
|
+
* apart offers **Remove** for a policy that does not exist.
|
|
72
|
+
*/
|
|
73
|
+
export const getMcpPolicy = async (token, scope, subjectUid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
74
|
+
try {
|
|
75
|
+
return await requestDatalayerAPI({
|
|
76
|
+
url: policyUrl(baseUrl, scope, subjectUid),
|
|
77
|
+
method: 'GET',
|
|
78
|
+
token,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// A layer nobody has written is a `404`, and it is an ordinary answer
|
|
83
|
+
// rather than a failure — most organizations have never written one.
|
|
84
|
+
if (isNotFound(error)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Replace one layer's rules.
|
|
92
|
+
*
|
|
93
|
+
* Replace, not merge: a policy is read whole and small, and merging would
|
|
94
|
+
* leave no way to express *removing* a rule — an owner clearing a denylist
|
|
95
|
+
* would find it still there.
|
|
96
|
+
*/
|
|
97
|
+
export const setMcpPolicy = async (token, scope, subjectUid, rules, options = {}, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
98
|
+
const query = options.expectedVersion === undefined
|
|
99
|
+
? ''
|
|
100
|
+
: `?expected_version=${encodeURIComponent(String(options.expectedVersion))}`;
|
|
101
|
+
try {
|
|
102
|
+
const response = await requestDatalayerAPI({
|
|
103
|
+
url: policyUrl(baseUrl, scope, subjectUid, query),
|
|
104
|
+
method: 'PUT',
|
|
105
|
+
token,
|
|
106
|
+
body: rules,
|
|
107
|
+
});
|
|
108
|
+
return response.policy ?? {};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (isConflict(error)) {
|
|
112
|
+
throw new McpPolicyConflict('This policy changed while you were editing it. Read it again and ' +
|
|
113
|
+
'reapply your change — otherwise one of the two edits is lost, and ' +
|
|
114
|
+
'whoever loses it is not told.');
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
/** Remove one layer, so it narrows nothing again. */
|
|
120
|
+
export const deleteMcpPolicy = async (token, scope, subjectUid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
121
|
+
await requestDatalayerAPI({
|
|
122
|
+
url: policyUrl(baseUrl, scope, subjectUid),
|
|
123
|
+
method: 'DELETE',
|
|
124
|
+
token,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
const statusOf = (error) => {
|
|
128
|
+
const candidate = error;
|
|
129
|
+
return candidate?.status ?? candidate?.response?.status;
|
|
130
|
+
};
|
|
131
|
+
const isNotFound = (error) => statusOf(error) === 404;
|
|
132
|
+
const isConflict = (error) => statusOf(error) === 409;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface OrganizationTeam {
|
|
2
|
+
uid: string;
|
|
3
|
+
handle: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* One organization's teams.
|
|
8
|
+
*
|
|
9
|
+
* A team with no uid is dropped rather than rendered: it addresses no
|
|
10
|
+
* policy layer, so a row for it would offer a form whose every write is a
|
|
11
|
+
* `404`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const listOrganizationTeams: (token: string, orgUid: string, baseUrl?: string) => Promise<OrganizationTeam[]>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/*
|
|
6
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
7
|
+
* Distributed under the terms of the Modified BSD License.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The teams inside one organization.
|
|
11
|
+
*
|
|
12
|
+
* Only what the MCP console needs of them: a name to show and a uid to
|
|
13
|
+
* address a policy layer with. Team membership, invitations and the rest
|
|
14
|
+
* belong to the organization's own settings, not here.
|
|
15
|
+
*
|
|
16
|
+
* @module api/iam/organizationTeams
|
|
17
|
+
*/
|
|
18
|
+
import { requestDatalayerAPI } from '../DatalayerApi.js';
|
|
19
|
+
import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants.js';
|
|
20
|
+
/**
|
|
21
|
+
* One organization's teams.
|
|
22
|
+
*
|
|
23
|
+
* A team with no uid is dropped rather than rendered: it addresses no
|
|
24
|
+
* policy layer, so a row for it would offer a form whose every write is a
|
|
25
|
+
* `404`.
|
|
26
|
+
*/
|
|
27
|
+
export const listOrganizationTeams = async (token, orgUid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
28
|
+
const response = await requestDatalayerAPI({
|
|
29
|
+
url: `${baseUrl}${API_BASE_PATHS.IAM}/organizations/` +
|
|
30
|
+
`${encodeURIComponent(orgUid)}/teams`,
|
|
31
|
+
method: 'GET',
|
|
32
|
+
token,
|
|
33
|
+
});
|
|
34
|
+
return (response.teams ?? [])
|
|
35
|
+
.filter(team => Boolean(team.uid))
|
|
36
|
+
.map(team => ({
|
|
37
|
+
uid: team.uid,
|
|
38
|
+
handle: team.handle_s ?? '',
|
|
39
|
+
// The handle is the fallback rather than the uid: a person picking a
|
|
40
|
+
// team from a list knows `data-science`, not `01ARZ3ND…`.
|
|
41
|
+
name: team.name_t || team.handle_s || team.uid,
|
|
42
|
+
}));
|
|
43
|
+
};
|