@k2b/cloud 0.6.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/package.json +128 -0
- package/public/logo.svg +1 -0
- package/scripts/app-favicon.test.ts +49 -0
- package/scripts/app-favicon.ts +51 -0
- package/scripts/build.ts +205 -0
- package/scripts/preload.ts +164 -0
- package/scripts/sync-recovery-smoke.ts +200 -0
- package/src/_internal/app-favicon.ts +2 -0
- package/src/_internal/app-presentation.ts +61 -0
- package/src/_internal/app-workspace-state.ts +24 -0
- package/src/_internal/bounded-json.ts +47 -0
- package/src/_internal/build-metadata.ts +9 -0
- package/src/_internal/capabilities.ts +1118 -0
- package/src/_internal/define-app.ts +801 -0
- package/src/_internal/heartbeat.ts +167 -0
- package/src/_internal/help-catalog.ts +241 -0
- package/src/_internal/help.ts +95 -0
- package/src/_internal/nats-connection.ts +34 -0
- package/src/_internal/page-responses.ts +40 -0
- package/src/_internal/process-sync.ts +117 -0
- package/src/_internal/readiness.ts +3 -0
- package/src/_internal/registry-validation.ts +136 -0
- package/src/_internal/registry-watch.ts +52 -0
- package/src/_internal/registry.ts +338 -0
- package/src/_internal/runtime-compatibility.ts +64 -0
- package/src/_internal/runtime-context.ts +84 -0
- package/src/_internal/runtime-watcher.ts +78 -0
- package/src/_internal/static-assets.ts +86 -0
- package/src/_internal/status-preserving-ssr.ts +49 -0
- package/src/access/PermissionEditor.tsx +486 -0
- package/src/access/ResourceApiKeys.tsx +271 -0
- package/src/access/messages.ts +121 -0
- package/src/access/ui.ts +16 -0
- package/src/account/Avatar.tsx +28 -0
- package/src/account/EntitySearch.tsx +363 -0
- package/src/account/avatar-upload-dialog.tsx +197 -0
- package/src/account/avatar-upload.ts +149 -0
- package/src/account/ui.ts +3 -0
- package/src/ai/admin.ts +47 -0
- package/src/ai/approval-routes.ts +130 -0
- package/src/ai/approvals.ts +139 -0
- package/src/ai/assistant-models.ts +61 -0
- package/src/ai/attachments.ts +40 -0
- package/src/ai/browser.ts +92 -0
- package/src/ai/capabilities.ts +714 -0
- package/src/ai/capability-execution.ts +151 -0
- package/src/ai/chat/blocks.render.test.tsx +1108 -0
- package/src/ai/chat/blocks.tsx +730 -0
- package/src/ai/chat/builtin-tools.tsx +428 -0
- package/src/ai/chat/capability-table.tsx +87 -0
- package/src/ai/chat/composer-adapter.ts +169 -0
- package/src/ai/chat/file-tools.tsx +81 -0
- package/src/ai/chat/message-actions.tsx +370 -0
- package/src/ai/chat/message-utils.ts +479 -0
- package/src/ai/chat/messages.ts +43 -0
- package/src/ai/chat/presentation.tsx +395 -0
- package/src/ai/chat/primitives.tsx +3 -0
- package/src/ai/chat/tool-disclosure.tsx +41 -0
- package/src/ai/chat/turn-navigator-utils.ts +28 -0
- package/src/ai/chat/turn-navigator.tsx +164 -0
- package/src/ai/chat/user-message.tsx +149 -0
- package/src/ai/chat/visual-tools.tsx +435 -0
- package/src/ai/chat/web-tools.tsx +202 -0
- package/src/ai/chat-task-contracts.ts +113 -0
- package/src/ai/chat-tasks.ts +952 -0
- package/src/ai/client/controller.ts +1211 -0
- package/src/ai/client/file-source.ts +95 -0
- package/src/ai/client/live-connection.ts +140 -0
- package/src/ai/client/projection.ts +178 -0
- package/src/ai/client/transport.ts +111 -0
- package/src/ai/compaction.ts +182 -0
- package/src/ai/credentials.ts +124 -0
- package/src/ai/default-tool-contracts.ts +108 -0
- package/src/ai/default-tools.ts +127 -0
- package/src/ai/enrich.ts +280 -0
- package/src/ai/executor.ts +1624 -0
- package/src/ai/fetch-file-tool.ts +313 -0
- package/src/ai/file-context.ts +92 -0
- package/src/ai/file-media-type.ts +28 -0
- package/src/ai/file-mount.ts +16 -0
- package/src/ai/file-tools.ts +566 -0
- package/src/ai/files-store.ts +390 -0
- package/src/ai/firecrawl-tools.ts +305 -0
- package/src/ai/grids-skill.ts +126 -0
- package/src/ai/http.ts +265 -0
- package/src/ai/index.ts +337 -0
- package/src/ai/kit-skill.ts +22 -0
- package/src/ai/limits.ts +5 -0
- package/src/ai/live-events.ts +289 -0
- package/src/ai/live-messages.ts +45 -0
- package/src/ai/live-outbox.ts +102 -0
- package/src/ai/live-routes.ts +412 -0
- package/src/ai/live.ts +2 -0
- package/src/ai/maintenance.ts +302 -0
- package/src/ai/markdown-pdf-tool.ts +95 -0
- package/src/ai/memories.ts +605 -0
- package/src/ai/memory-learning-runs.ts +222 -0
- package/src/ai/memory-learning.ts +619 -0
- package/src/ai/memory-tool.ts +120 -0
- package/src/ai/memory-workflow-evidence.ts +158 -0
- package/src/ai/migrate.ts +2082 -0
- package/src/ai/model-access-migrate.ts +30 -0
- package/src/ai/model-access.ts +191 -0
- package/src/ai/personal-agent.ts +6 -0
- package/src/ai/prefs.ts +72 -0
- package/src/ai/project-tool.ts +94 -0
- package/src/ai/projects-routes.ts +277 -0
- package/src/ai/projects.ts +902 -0
- package/src/ai/protocol.ts +279 -0
- package/src/ai/provider.ts +73 -0
- package/src/ai/public-projection.ts +25 -0
- package/src/ai/resource-markers.ts +31 -0
- package/src/ai/resource-refs.ts +70 -0
- package/src/ai/routes.ts +1131 -0
- package/src/ai/runtime-tools.ts +39 -0
- package/src/ai/runtime.ts +544 -0
- package/src/ai/settings.ts +529 -0
- package/src/ai/short-id.ts +45 -0
- package/src/ai/skill-catalog.ts +73 -0
- package/src/ai/skill-format.ts +145 -0
- package/src/ai/skill-seeds.ts +430 -0
- package/src/ai/skill-tool.ts +81 -0
- package/src/ai/skills-routes.ts +143 -0
- package/src/ai/skills.ts +934 -0
- package/src/ai/solid.ts +27 -0
- package/src/ai/store.ts +3113 -0
- package/src/ai/stream.ts +308 -0
- package/src/ai/structured-runs.ts +66 -0
- package/src/ai/structured.ts +186 -0
- package/src/ai/system-prompt.ts +170 -0
- package/src/ai/task-prompt.ts +22 -0
- package/src/ai/timeline.ts +148 -0
- package/src/ai/tool-audit.ts +177 -0
- package/src/ai/tool-result-budget.ts +16 -0
- package/src/ai/tools.ts +155 -0
- package/src/ai/types.ts +967 -0
- package/src/ai/ui.tsx +39 -0
- package/src/ai/usage-migrate.ts +81 -0
- package/src/ai/usage.ts +315 -0
- package/src/ai/validate.ts +54 -0
- package/src/ai/vision-tool.ts +79 -0
- package/src/api/accounts-entities.ts +174 -0
- package/src/api/admin-ai-projects.ts +74 -0
- package/src/api/admin-ai-skills.ts +76 -0
- package/src/api/admin-ai-usage.ts +42 -0
- package/src/api/admin-core-settings.ts +508 -0
- package/src/api/admin-identity.ts +148 -0
- package/src/api/admin-lifecycle.ts +206 -0
- package/src/api/admin-linux-identities.ts +66 -0
- package/src/api/announcements.ts +131 -0
- package/src/api/app-approval.ts +129 -0
- package/src/api/apps.ts +51 -0
- package/src/api/auth/schemas.ts +54 -0
- package/src/api/auth.ts +370 -0
- package/src/api/capabilities.ts +658 -0
- package/src/api/help-messages.ts +19 -0
- package/src/api/help.ts +78 -0
- package/src/api/index.ts +108 -0
- package/src/api/mcp.ts +744 -0
- package/src/api/me-rail.ts +45 -0
- package/src/api/me.ts +753 -0
- package/src/api/search/schemas.ts +79 -0
- package/src/api/search.bench.ts +185 -0
- package/src/api/search.ts +407 -0
- package/src/api/sync-ops.ts +79 -0
- package/src/api/widgets.ts +143 -0
- package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
- package/src/browser/CloudResourceSearch.tsx +791 -0
- package/src/browser/app-approval-vault.ts +305 -0
- package/src/browser/app-approval.ts +234 -0
- package/src/browser/live-websocket.ts +268 -0
- package/src/browser/locale-preference.ts +25 -0
- package/src/browser/notification-target.ts +27 -0
- package/src/browser/notification-timeout.ts +14 -0
- package/src/browser/notifications.ts +181 -0
- package/src/browser/resource-clipboard.ts +70 -0
- package/src/browser/resource-picker.tsx +39 -0
- package/src/browser/resource-search-messages.ts +55 -0
- package/src/browser/resource-search.ts +27 -0
- package/src/capabilities/client.ts +149 -0
- package/src/capabilities/response.ts +39 -0
- package/src/capabilities/server.ts +215 -0
- package/src/capabilities/signals.ts +4 -0
- package/src/capabilities/testing.ts +12 -0
- package/src/capabilities/types.ts +32 -0
- package/src/cli/access.ts +499 -0
- package/src/cli/account.ts +382 -0
- package/src/cli/admin/account-administration.ts +45 -0
- package/src/cli/admin/account-categories.ts +49 -0
- package/src/cli/admin/ai-usage.ts +118 -0
- package/src/cli/admin/app-sign-in.ts +49 -0
- package/src/cli/admin/data.ts +343 -0
- package/src/cli/admin/documentation.ts +34 -0
- package/src/cli/admin/gateway.ts +155 -0
- package/src/cli/admin/index.ts +61 -0
- package/src/cli/admin/instance.ts +250 -0
- package/src/cli/admin/jobs.ts +223 -0
- package/src/cli/admin/legal.ts +122 -0
- package/src/cli/admin/linux.ts +53 -0
- package/src/cli/admin/logs.ts +324 -0
- package/src/cli/admin/metrics.ts +142 -0
- package/src/cli/admin/nats.ts +167 -0
- package/src/cli/admin/notifications.ts +516 -0
- package/src/cli/admin/shared.ts +113 -0
- package/src/cli/admin/sync.ts +188 -0
- package/src/cli/admin/telemetry.ts +314 -0
- package/src/cli/admin/webhooks.ts +222 -0
- package/src/cli/admin/workflows.ts +367 -0
- package/src/cli/apps.ts +40 -0
- package/src/cli/capabilities.ts +239 -0
- package/src/cli/commands.ts +604 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/locale.ts +31 -0
- package/src/cli/output.ts +58 -0
- package/src/clients/core.ts +20 -0
- package/src/config/env.ts +54 -0
- package/src/config/index.ts +6 -0
- package/src/contracts/account-categories.ts +62 -0
- package/src/contracts/announcements.ts +121 -0
- package/src/contracts/app-approval.ts +197 -0
- package/src/contracts/app.ts +162 -0
- package/src/contracts/capabilities.ts +484 -0
- package/src/contracts/index.ts +14 -0
- package/src/contracts/notification-live.ts +66 -0
- package/src/contracts/notification-types.ts +235 -0
- package/src/contracts/posix.ts +69 -0
- package/src/contracts/profile.ts +85 -0
- package/src/contracts/rail-preferences.ts +53 -0
- package/src/contracts/registry.ts +110 -0
- package/src/contracts/resource-clipboard.ts +33 -0
- package/src/contracts/settings-types.ts +98 -0
- package/src/contracts/shared.ts +408 -0
- package/src/contracts/user-notifications.ts +101 -0
- package/src/contracts/widgets.ts +183 -0
- package/src/desktop/index.ts +704 -0
- package/src/desktop/solid.tsx +938 -0
- package/src/index.ts +58 -0
- package/src/server/actor.ts +41 -0
- package/src/server/api/index.ts +2 -0
- package/src/server/api/respond.ts +96 -0
- package/src/server/api-client.ts +54 -0
- package/src/server/app-context.ts +39 -0
- package/src/server/help.ts +187 -0
- package/src/server/index.ts +89 -0
- package/src/server/locale.ts +55 -0
- package/src/server/middleware/auth.ts +386 -0
- package/src/server/middleware/index.ts +28 -0
- package/src/server/middleware/invocation.ts +55 -0
- package/src/server/middleware/middleware.ts +65 -0
- package/src/server/middleware/openapi.ts +126 -0
- package/src/server/middleware/rate-limit.ts +125 -0
- package/src/server/middleware/request-logger.ts +41 -0
- package/src/server/middleware/route-template.ts +46 -0
- package/src/server/middleware/runtime.ts +22 -0
- package/src/server/middleware/settings.ts +55 -0
- package/src/server/middleware/validator.ts +49 -0
- package/src/server/middleware/workload.ts +22 -0
- package/src/server/ratelimit.ts +151 -0
- package/src/server/request-cookies.ts +18 -0
- package/src/server/services/access.ts +644 -0
- package/src/server/services/freeipa/client.ts +137 -0
- package/src/server/services/freeipa/index.ts +9 -0
- package/src/server/services/freeipa/session.ts +123 -0
- package/src/server/services/freeipa/test-certificates.ts +44 -0
- package/src/server/services/freeipa/tls.ts +44 -0
- package/src/server/services/freeipa/transport.ts +116 -0
- package/src/server/services/freeipa/util.ts +59 -0
- package/src/server/services/geo.ts +198 -0
- package/src/server/services/index.ts +48 -0
- package/src/server/services/pagination.ts +22 -0
- package/src/server/services/services.ts +13 -0
- package/src/server/time.ts +33 -0
- package/src/services/account-category-policy.ts +51 -0
- package/src/services/account-lifecycle/audit.ts +41 -0
- package/src/services/account-lifecycle/index.ts +940 -0
- package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
- package/src/services/account-lifecycle/notification-sender.ts +13 -0
- package/src/services/account-lifecycle/scheduler.ts +506 -0
- package/src/services/account-model.ts +22 -0
- package/src/services/accounts/app.ts +1671 -0
- package/src/services/accounts/authz.ts +44 -0
- package/src/services/accounts/avatar.ts +120 -0
- package/src/services/accounts/base-group.ts +11 -0
- package/src/services/accounts/base-user.ts +46 -0
- package/src/services/accounts/duplicate-emails.ts +82 -0
- package/src/services/accounts/entities.ts +622 -0
- package/src/services/accounts/group-sql.ts +106 -0
- package/src/services/accounts/groups.ts +272 -0
- package/src/services/accounts/index.ts +14 -0
- package/src/services/accounts/ipa-data.ts +66 -0
- package/src/services/accounts/lifecycle.ts +2 -0
- package/src/services/accounts/local-groups.ts +499 -0
- package/src/services/accounts/model.ts +129 -0
- package/src/services/accounts/notification-sender.ts +31 -0
- package/src/services/accounts/posix.ts +386 -0
- package/src/services/accounts/request-policy.ts +11 -0
- package/src/services/accounts/switching.ts +126 -0
- package/src/services/accounts/users.ts +745 -0
- package/src/services/announcements/index.ts +224 -0
- package/src/services/app-approval.ts +498 -0
- package/src/services/audit/index.ts +438 -0
- package/src/services/auth-flows/index.ts +15 -0
- package/src/services/auth-flows/ipa.ts +197 -0
- package/src/services/auth-flows/magic-link.ts +185 -0
- package/src/services/auth-flows/notification-sender.ts +10 -0
- package/src/services/auth-flows/password-reset.ts +268 -0
- package/src/services/auth-flows/proxy-return.ts +49 -0
- package/src/services/document-extraction.ts +148 -0
- package/src/services/freeipa-config.ts +179 -0
- package/src/services/gateway.ts +186 -0
- package/src/services/identity/constants.ts +18 -0
- package/src/services/identity/index.ts +52 -0
- package/src/services/identity/invocation-actor.ts +120 -0
- package/src/services/identity/invocation-authority.ts +53 -0
- package/src/services/identity/invocation-operations.ts +15 -0
- package/src/services/identity/invocation-token.ts +276 -0
- package/src/services/identity/key-config.ts +41 -0
- package/src/services/identity/key-ring.ts +530 -0
- package/src/services/identity/metrics.ts +29 -0
- package/src/services/identity/routes.ts +18 -0
- package/src/services/identity/runtime-config.ts +88 -0
- package/src/services/identity/session-token.ts +152 -0
- package/src/services/identity/signer-refresh.ts +44 -0
- package/src/services/identity/workload-auth.ts +64 -0
- package/src/services/index.ts +187 -0
- package/src/services/ipa/auth.ts +216 -0
- package/src/services/ipa/connection.ts +57 -0
- package/src/services/ipa/effective-groups.ts +70 -0
- package/src/services/ipa/groups.ts +722 -0
- package/src/services/ipa/guard.ts +17 -0
- package/src/services/ipa/index.ts +17 -0
- package/src/services/ipa/posix.ts +19 -0
- package/src/services/ipa/profile.ts +132 -0
- package/src/services/ipa/runtime.ts +28 -0
- package/src/services/ipa/search.ts +154 -0
- package/src/services/ipa/service-account.ts +15 -0
- package/src/services/ipa/sync-planning.ts +100 -0
- package/src/services/ipa/sync.ts +862 -0
- package/src/services/ipa/users.ts +806 -0
- package/src/services/legal-consent.ts +76 -0
- package/src/services/logging/index.ts +477 -0
- package/src/services/logging/redaction.ts +20 -0
- package/src/services/logging/trace.ts +1231 -0
- package/src/services/mandates/index.ts +855 -0
- package/src/services/mandates/policy.ts +85 -0
- package/src/services/network-security.ts +31 -0
- package/src/services/notifications/batches.ts +722 -0
- package/src/services/notifications/browser.ts +252 -0
- package/src/services/notifications/catalog.ts +113 -0
- package/src/services/notifications/channels.ts +104 -0
- package/src/services/notifications/dispatcher.ts +139 -0
- package/src/services/notifications/email.ts +118 -0
- package/src/services/notifications/index.ts +633 -0
- package/src/services/notifications/live.ts +50 -0
- package/src/services/notifications/observability.ts +407 -0
- package/src/services/notifications/platform.ts +421 -0
- package/src/services/notifications/runtime.ts +120 -0
- package/src/services/notifications/user.ts +248 -0
- package/src/services/notifications/web-push-transport.ts +135 -0
- package/src/services/oauth-tokens.ts +209 -0
- package/src/services/outbox.ts +187 -0
- package/src/services/pdf/gotenberg.ts +290 -0
- package/src/services/pdf/index.ts +43 -0
- package/src/services/pdf/markdown.ts +209 -0
- package/src/services/pdf/template-preview.ts +122 -0
- package/src/services/postgres.ts +76 -0
- package/src/services/providers/index.ts +27 -0
- package/src/services/providers/local/auth.ts +51 -0
- package/src/services/providers/local/index.ts +4 -0
- package/src/services/providers/local/users.ts +263 -0
- package/src/services/rail-preferences.ts +27 -0
- package/src/services/runtime-lifecycle.ts +191 -0
- package/src/services/secrets.ts +10 -0
- package/src/services/service-account-credentials.ts +891 -0
- package/src/services/service-accounts.ts +184 -0
- package/src/services/session/index.ts +236 -0
- package/src/services/session/test-fixture.ts +18 -0
- package/src/services/session/user.ts +144 -0
- package/src/services/settings/api.ts +61 -0
- package/src/services/settings/app.ts +86 -0
- package/src/services/settings/core-settings.ts +796 -0
- package/src/services/settings/crypto.ts +69 -0
- package/src/services/settings/defaults.ts +374 -0
- package/src/services/settings/index.ts +215 -0
- package/src/services/settings/namespace.ts +9 -0
- package/src/services/settings/snapshot.ts +49 -0
- package/src/services/settings/store.ts +241 -0
- package/src/services/settings/templates.ts +4 -0
- package/src/services/sync-ops.ts +403 -0
- package/src/services/topic-cursor.ts +22 -0
- package/src/services/weather/forecast.ts +323 -0
- package/src/services/weather/geo.ts +119 -0
- package/src/services/weather/index.ts +67 -0
- package/src/services/weather/location.ts +24 -0
- package/src/services/weather/locations.ts +133 -0
- package/src/services/weather/migrate.ts +45 -0
- package/src/services/weather/settings.ts +66 -0
- package/src/services/weather/short-id.ts +57 -0
- package/src/services/weather/types.ts +61 -0
- package/src/services/weather/ui.ts +50 -0
- package/src/services/webauthn.ts +385 -0
- package/src/shared/account-action-notice.ts +70 -0
- package/src/shared/account-display.ts +15 -0
- package/src/shared/account-session.ts +15 -0
- package/src/shared/ai-platform-prompt.ts +134 -0
- package/src/shared/ai-usage.ts +49 -0
- package/src/shared/app-presentation.ts +48 -0
- package/src/shared/app-url.ts +12 -0
- package/src/shared/branding.ts +14 -0
- package/src/shared/capability-messages.ts +70 -0
- package/src/shared/email-html.ts +54 -0
- package/src/shared/format.ts +91 -0
- package/src/shared/help.ts +68 -0
- package/src/shared/icons.ts +400 -0
- package/src/shared/index.ts +54 -0
- package/src/shared/locale.ts +48 -0
- package/src/shared/login-method.ts +44 -0
- package/src/shared/markdown/client.ts +130 -0
- package/src/shared/markdown/extensions/code.ts +105 -0
- package/src/shared/markdown/extensions/guided-help.ts +100 -0
- package/src/shared/markdown/extensions/images.ts +79 -0
- package/src/shared/markdown/extensions/info-blocks.ts +97 -0
- package/src/shared/markdown/extensions/katex.ts +120 -0
- package/src/shared/markdown/extensions/links.ts +43 -0
- package/src/shared/markdown/extensions/mark.ts +48 -0
- package/src/shared/markdown/extensions/sub-sup.ts +60 -0
- package/src/shared/markdown/extensions/tables.ts +109 -0
- package/src/shared/markdown/extensions/task-list.ts +53 -0
- package/src/shared/markdown/formula.ts +1187 -0
- package/src/shared/markdown/index.ts +193 -0
- package/src/shared/markdown/shared.ts +36 -0
- package/src/shared/mock-cover.ts +130 -0
- package/src/shared/network-address.ts +106 -0
- package/src/shared/redirect.ts +56 -0
- package/src/shared/template-rendering.ts +140 -0
- package/src/shared/theme.ts +68 -0
- package/src/shared/time.ts +13 -0
- package/src/ssr/AdminLayout.tsx +53 -0
- package/src/ssr/AdminSidebar.tsx +50 -0
- package/src/ssr/AppLaunchpad.island.tsx +236 -0
- package/src/ssr/AppWorkspaceController.island.tsx +21 -0
- package/src/ssr/BrowserNotifications.island.tsx +135 -0
- package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
- package/src/ssr/GlobalSearchDialog.tsx +35 -0
- package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
- package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
- package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
- package/src/ssr/Layout.render.test.tsx +148 -0
- package/src/ssr/Layout.tsx +292 -0
- package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
- package/src/ssr/LayoutFooter.tsx +22 -0
- package/src/ssr/LayoutHeader.tsx +81 -0
- package/src/ssr/LayoutHelp.tsx +869 -0
- package/src/ssr/LayoutPreferences.island.tsx +36 -0
- package/src/ssr/LayoutRail.tsx +58 -0
- package/src/ssr/MinimalLayout.tsx +46 -0
- package/src/ssr/PageError.tsx +29 -0
- package/src/ssr/ProfilePreferences.island.tsx +92 -0
- package/src/ssr/RailApps.island.tsx +61 -0
- package/src/ssr/RailEditor.tsx +293 -0
- package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
- package/src/ssr/TimezoneCookie.island.tsx +23 -0
- package/src/ssr/admin-active-link.ts +47 -0
- package/src/ssr/admin-navigation.ts +78 -0
- package/src/ssr/app-appearance.ts +23 -0
- package/src/ssr/app-navigation.ts +41 -0
- package/src/ssr/help-messages.ts +104 -0
- package/src/ssr/index.ts +14 -0
- package/src/ssr/islands/SearchBar.island.tsx +80 -0
- package/src/ssr/islands/index.ts +16 -0
- package/src/ssr/layout-context.ts +17 -0
- package/src/ssr/layout-help-markdown.ts +9 -0
- package/src/ssr/layout-help-navigation.ts +24 -0
- package/src/ssr/layout-help-url.ts +5 -0
- package/src/ssr/layout-runtime.ts +22 -0
- package/src/ssr/page-error-messages.ts +25 -0
- package/src/ssr/platform-messages.ts +135 -0
- package/src/ssr/preference-controller.ts +44 -0
- package/src/ssr/profile-preferences-messages.ts +29 -0
- package/src/ssr/rail-context.ts +21 -0
- package/src/ssr/rail-messages.ts +66 -0
- package/src/ssr/rail-navigation.ts +60 -0
- package/src/ssr/runtime.ts +31 -0
- package/src/ssr/url-filter.ts +127 -0
- package/src/styles/base-popover.css +36 -0
- package/src/styles/effects.css +851 -0
- package/src/styles/global.css +268 -0
- package/src/styles/input.css +58 -0
- package/src/styles/tokens.css +230 -0
- package/src/styles/utilities-buttons.css +405 -0
- package/src/styles/utilities-data.css +33 -0
- package/src/styles/utilities-feedback.css +232 -0
- package/src/styles/utilities-layout.css +603 -0
- package/src/styles/utilities-markdown-editor.css +525 -0
- package/src/styles/utilities-markdown-table.css +234 -0
- package/src/styles/utilities-navigation.css +1054 -0
- package/src/types/ambient.d.ts +24 -0
- package/src/workflows/ai/index.ts +17 -0
- package/src/workflows/ai/runtime.ts +286 -0
- package/src/workflows/ai/store.ts +314 -0
- package/src/workflows/ai/types.ts +111 -0
- package/src/workflows/ai-actions.ts +269 -0
- package/src/workflows/authoring.ts +88 -0
- package/src/workflows/builtins.ts +193 -0
- package/src/workflows/contracts.ts +280 -0
- package/src/workflows/definition.ts +334 -0
- package/src/workflows/editor.ts +113 -0
- package/src/workflows/index.ts +71 -0
- package/src/workflows/language/binder.ts +45 -0
- package/src/workflows/language/canonical.ts +29 -0
- package/src/workflows/language/compiler.ts +396 -0
- package/src/workflows/language/expressions.ts +34 -0
- package/src/workflows/language/index.ts +20 -0
- package/src/workflows/language/references.ts +55 -0
- package/src/workflows/language/schema.ts +134 -0
- package/src/workflows/language/strict-yaml.ts +131 -0
- package/src/workflows/manifest.ts +24 -0
- package/src/workflows/module.ts +12 -0
- package/src/workflows/runtime/coordinator.ts +115 -0
- package/src/workflows/runtime/executor.ts +1003 -0
- package/src/workflows/runtime/index.ts +60 -0
- package/src/workflows/runtime/instant.ts +8 -0
- package/src/workflows/runtime/ports.ts +213 -0
- package/src/workflows/runtime/schedule.ts +203 -0
- package/src/workflows/runtime/trigger.ts +37 -0
- package/src/workflows/runtime/wake.ts +13 -0
- package/src/workflows/store/actions.ts +438 -0
- package/src/workflows/store/budget.ts +145 -0
- package/src/workflows/store/definitions.ts +345 -0
- package/src/workflows/store/events.ts +383 -0
- package/src/workflows/store/index.ts +107 -0
- package/src/workflows/store/observability.ts +594 -0
- package/src/workflows/store/runs.ts +975 -0
- package/src/workflows/store/transaction.ts +23 -0
- package/src/workflows/store/worker.ts +439 -0
- package/src/workflows/testing/conformance.ts +72 -0
- package/src/workflows/testing/index.ts +11 -0
- package/src/workflows/testing/process-fixtures.ts +275 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import { Hono, type MiddlewareHandler } from "hono";
|
|
2
|
+
import { describeRoute } from "hono-openapi";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { readBoundedJson } from "../_internal/bounded-json";
|
|
5
|
+
import { resolveCapabilityManifestPresentation } from "../_internal/capabilities";
|
|
6
|
+
import { getCapability, listApps } from "../_internal/registry";
|
|
7
|
+
import {
|
|
8
|
+
CAPABILITY_FRAMEWORK_ERROR_CODES,
|
|
9
|
+
CAPABILITY_MAX_CATALOG_BYTES,
|
|
10
|
+
CAPABILITY_MAX_REQUEST_BYTES,
|
|
11
|
+
CAPABILITY_MAX_RESULT_BYTES,
|
|
12
|
+
CAPABILITY_PROTOCOL_VERSION,
|
|
13
|
+
CapabilityActionReviewSchema,
|
|
14
|
+
type CapabilityCatalog,
|
|
15
|
+
CapabilityCatalogSchema,
|
|
16
|
+
CapabilityErrorSchema,
|
|
17
|
+
CapabilityIdempotencyKeySchema,
|
|
18
|
+
capabilityResultJsonSchema,
|
|
19
|
+
} from "../contracts/capabilities";
|
|
20
|
+
import type { AppRegistryEntry, CapabilityRegistryEntry } from "../contracts/registry";
|
|
21
|
+
import {
|
|
22
|
+
type AuthContext,
|
|
23
|
+
auth,
|
|
24
|
+
getLocale,
|
|
25
|
+
jsonResponse,
|
|
26
|
+
LOCALE_HEADER,
|
|
27
|
+
preferredLocale,
|
|
28
|
+
type RequestAuthority,
|
|
29
|
+
rejectReservedWorkloadCredential,
|
|
30
|
+
requiresAuth,
|
|
31
|
+
v,
|
|
32
|
+
} from "../server";
|
|
33
|
+
import { logger } from "../services";
|
|
34
|
+
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
35
|
+
import { capabilityInvocationOperation } from "../services/identity/invocation-operations";
|
|
36
|
+
import type { InvocationAuthority } from "../services/identity/invocation-token";
|
|
37
|
+
import { normalizeInvocationRequestId, signInvocationToken } from "../services/identity/invocation-token";
|
|
38
|
+
import { withActiveIdentitySigner } from "../services/identity/key-ring";
|
|
39
|
+
import { withMandateIssueAuthority } from "../services/mandates";
|
|
40
|
+
import { resolveAppPresentation } from "../shared/app-presentation";
|
|
41
|
+
import { capabilityMessages } from "../shared/capability-messages";
|
|
42
|
+
|
|
43
|
+
const log = logger("capabilities");
|
|
44
|
+
const QUERY_TIMEOUT_MS = 15_000;
|
|
45
|
+
const ACTION_TIMEOUT_MS = 30_000;
|
|
46
|
+
const DEFAULT_CATALOG_LIMIT = 10;
|
|
47
|
+
const MAX_CATALOG_LIMIT = 25;
|
|
48
|
+
const MAX_SCHEMA_VALIDATORS = 512;
|
|
49
|
+
|
|
50
|
+
const schemaValidators = new Map<string, z.ZodType | null>();
|
|
51
|
+
|
|
52
|
+
const schemaValidator = (key: string, schema: Record<string, unknown>): z.ZodType | null => {
|
|
53
|
+
if (schemaValidators.has(key)) return schemaValidators.get(key) ?? null;
|
|
54
|
+
let validator: z.ZodType | null = null;
|
|
55
|
+
try {
|
|
56
|
+
validator = z.fromJSONSchema(schema);
|
|
57
|
+
} catch {
|
|
58
|
+
// A live manifest with an unsupported schema is unusable, but must not
|
|
59
|
+
// crash the dispatcher serving unrelated applications.
|
|
60
|
+
}
|
|
61
|
+
if (schemaValidators.size >= MAX_SCHEMA_VALIDATORS) {
|
|
62
|
+
const oldest = schemaValidators.keys().next().value;
|
|
63
|
+
if (oldest) schemaValidators.delete(oldest);
|
|
64
|
+
}
|
|
65
|
+
schemaValidators.set(key, validator);
|
|
66
|
+
return validator;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const CapabilityInvocationRequestSchema = z.object({ input: z.unknown() }).strict();
|
|
70
|
+
const CapabilityCatalogQuerySchema = z
|
|
71
|
+
.object({
|
|
72
|
+
cursor: z.string().max(80).optional(),
|
|
73
|
+
limit: z.coerce.number().int().min(1).max(MAX_CATALOG_LIMIT).default(DEFAULT_CATALOG_LIMIT),
|
|
74
|
+
})
|
|
75
|
+
.strict();
|
|
76
|
+
export type CapabilityRouteDependencies = {
|
|
77
|
+
listApps?: () => Promise<AppRegistryEntry[]>;
|
|
78
|
+
getCapability?: (appId: string) => Promise<CapabilityRegistryEntry | null>;
|
|
79
|
+
fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
80
|
+
authenticate?: MiddlewareHandler<AuthContext>;
|
|
81
|
+
signInvocation?: typeof signInvocationToken;
|
|
82
|
+
withActiveSigner?: typeof withActiveIdentitySigner;
|
|
83
|
+
withMandateIssueAuthority?: typeof withMandateIssueAuthority;
|
|
84
|
+
queryTimeoutMs?: number;
|
|
85
|
+
actionTimeoutMs?: number;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type CapabilityDispatchDependencies = Pick<
|
|
89
|
+
CapabilityRouteDependencies,
|
|
90
|
+
"getCapability" | "fetch" | "queryTimeoutMs" | "actionTimeoutMs" | "signInvocation" | "withActiveSigner" | "withMandateIssueAuthority"
|
|
91
|
+
>;
|
|
92
|
+
|
|
93
|
+
type MandatedCapabilityAuthority = {
|
|
94
|
+
mandateId: string;
|
|
95
|
+
mandateRevision: number;
|
|
96
|
+
ownerAppId: string;
|
|
97
|
+
/** Internal proof from a completed approval flow. Public broker input cannot set this. */
|
|
98
|
+
actionApproval?: "approved";
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const invocationAuthorityFromMandate = (authority: {
|
|
102
|
+
mandateId: string;
|
|
103
|
+
mandateRevision: number;
|
|
104
|
+
subject: { type: "user" | "service_account"; id: string };
|
|
105
|
+
workloadType: string;
|
|
106
|
+
workloadId: string;
|
|
107
|
+
}): InvocationAuthority => ({
|
|
108
|
+
sub: authority.subject.id,
|
|
109
|
+
principal_type: authority.subject.type,
|
|
110
|
+
access_subject_type: authority.subject.type,
|
|
111
|
+
access_subject_id: authority.subject.id,
|
|
112
|
+
credential_kind: "mandate",
|
|
113
|
+
scopes: [],
|
|
114
|
+
mandate_id: authority.mandateId,
|
|
115
|
+
mandate_revision: authority.mandateRevision,
|
|
116
|
+
workload_type: authority.workloadType,
|
|
117
|
+
workload_id: authority.workloadId,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export const loadCapabilityCatalogPage = async (
|
|
121
|
+
query: { cursor?: string; limit: number },
|
|
122
|
+
dependencies: Pick<CapabilityRouteDependencies, "listApps" | "getCapability"> = {},
|
|
123
|
+
locale?: string,
|
|
124
|
+
): Promise<CapabilityCatalog> => {
|
|
125
|
+
if (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > MAX_CATALOG_LIMIT) {
|
|
126
|
+
throw new RangeError(`Capability catalog limit must be between 1 and ${MAX_CATALOG_LIMIT}`);
|
|
127
|
+
}
|
|
128
|
+
const registry = dependencies.listApps ?? listApps;
|
|
129
|
+
const capabilityLookup = dependencies.getCapability ?? getCapability;
|
|
130
|
+
const entries = await registry();
|
|
131
|
+
const liveApps = entries
|
|
132
|
+
.filter((entry) => entry.capabilities?.protocolVersion === CAPABILITY_PROTOCOL_VERSION)
|
|
133
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
134
|
+
const start = query.cursor ? liveApps.findIndex((entry) => entry.id > query.cursor!) : 0;
|
|
135
|
+
const offset = start < 0 ? liveApps.length : start;
|
|
136
|
+
const apps: CapabilityCatalog["apps"] = [];
|
|
137
|
+
let bytes = 0;
|
|
138
|
+
let lastScannedAppId: string | undefined;
|
|
139
|
+
const envelopeBytes = new TextEncoder().encode(
|
|
140
|
+
JSON.stringify({
|
|
141
|
+
protocolVersion: CAPABILITY_PROTOCOL_VERSION,
|
|
142
|
+
apps: [],
|
|
143
|
+
page: { hasMore: true, nextCursor: "x".repeat(80) },
|
|
144
|
+
}),
|
|
145
|
+
).byteLength;
|
|
146
|
+
for (const entry of liveApps.slice(offset)) {
|
|
147
|
+
if (apps.length === query.limit) break;
|
|
148
|
+
const capability = await capabilityLookup(entry.id);
|
|
149
|
+
if (!capability || capability.manifest.manifestHash !== entry.capabilities?.manifestHash) {
|
|
150
|
+
lastScannedAppId = entry.id;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const presentedEntry = locale ? resolveAppPresentation(entry, locale) : entry;
|
|
154
|
+
const projected = {
|
|
155
|
+
appId: capability.appId,
|
|
156
|
+
appName: presentedEntry.name,
|
|
157
|
+
appIcon: capability.appIcon,
|
|
158
|
+
appDescription: presentedEntry.description,
|
|
159
|
+
manifest: locale ? resolveCapabilityManifestPresentation(capability.manifest, capability.presentation, locale) : capability.manifest,
|
|
160
|
+
};
|
|
161
|
+
const projectedBytes = new TextEncoder().encode(JSON.stringify(projected)).byteLength;
|
|
162
|
+
const separatingCommas = apps.length;
|
|
163
|
+
if (apps.length > 0 && envelopeBytes + bytes + separatingCommas + projectedBytes > CAPABILITY_MAX_CATALOG_BYTES) break;
|
|
164
|
+
apps.push(projected);
|
|
165
|
+
bytes += projectedBytes;
|
|
166
|
+
lastScannedAppId = entry.id;
|
|
167
|
+
}
|
|
168
|
+
const hasMore = lastScannedAppId !== undefined && liveApps.some((entry) => entry.id > lastScannedAppId);
|
|
169
|
+
const page: CapabilityCatalog["page"] =
|
|
170
|
+
hasMore && lastScannedAppId ? { hasMore: true, nextCursor: lastScannedAppId } : { hasMore: false };
|
|
171
|
+
return {
|
|
172
|
+
protocolVersion: CAPABILITY_PROTOCOL_VERSION,
|
|
173
|
+
apps,
|
|
174
|
+
page,
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const errorResponse = (code: string, message: string, status: number, details?: Record<string, unknown>) => ({
|
|
179
|
+
body: { code, message, ...(details ? { details } : {}) },
|
|
180
|
+
status,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const capabilityJsonResponse = (body: unknown, status: number): Response =>
|
|
184
|
+
Response.json(body, {
|
|
185
|
+
status,
|
|
186
|
+
headers: { "content-type": "application/json" },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const waitWithin = <T>(value: Promise<T>, signal: AbortSignal): Promise<T> =>
|
|
190
|
+
new Promise<T>((resolve, reject) => {
|
|
191
|
+
const aborted = () => reject(signal.reason);
|
|
192
|
+
if (signal.aborted) return aborted();
|
|
193
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
194
|
+
value.then(
|
|
195
|
+
(result) => {
|
|
196
|
+
signal.removeEventListener("abort", aborted);
|
|
197
|
+
resolve(result);
|
|
198
|
+
},
|
|
199
|
+
(error) => {
|
|
200
|
+
signal.removeEventListener("abort", aborted);
|
|
201
|
+
reject(error);
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Dispatch one already-parsed capability invocation through the live registry.
|
|
208
|
+
* HTTP, CLI, and MCP callers share this exact app lookup, credential forwarding,
|
|
209
|
+
* schema pinning, timeout, and response validation path.
|
|
210
|
+
*/
|
|
211
|
+
export const dispatchCapability = async (params: {
|
|
212
|
+
request: Request;
|
|
213
|
+
kind: "queries" | "actions";
|
|
214
|
+
review?: boolean;
|
|
215
|
+
appId: string;
|
|
216
|
+
capabilityId: string;
|
|
217
|
+
input: unknown;
|
|
218
|
+
locale?: string;
|
|
219
|
+
authority?: RequestAuthority;
|
|
220
|
+
mandate?: MandatedCapabilityAuthority;
|
|
221
|
+
callingAppId?: string;
|
|
222
|
+
dependencies?: CapabilityDispatchDependencies;
|
|
223
|
+
}): Promise<Response> => {
|
|
224
|
+
const messages = capabilityMessages(params.locale ?? preferredLocale(params.request.headers));
|
|
225
|
+
const registryEntry = params.dependencies?.getCapability ?? getCapability;
|
|
226
|
+
const fetchUpstream = params.dependencies?.fetch ?? globalThis.fetch;
|
|
227
|
+
let entry: CapabilityRegistryEntry | null;
|
|
228
|
+
try {
|
|
229
|
+
entry = await registryEntry(params.appId);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
log.warn("Capability registry unavailable", {
|
|
232
|
+
appId: params.appId,
|
|
233
|
+
error: error instanceof Error ? error.message : String(error),
|
|
234
|
+
});
|
|
235
|
+
const unavailable = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable, messages.registryUnavailable, 503);
|
|
236
|
+
return capabilityJsonResponse(unavailable.body, unavailable.status);
|
|
237
|
+
}
|
|
238
|
+
if (!entry) {
|
|
239
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable, messages.appUnavailable({ appId: params.appId }), 503);
|
|
240
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const operations = params.kind === "queries" ? entry.manifest.queries : entry.manifest.actions;
|
|
244
|
+
const operation = operations.find((candidate) => candidate.localId === params.capabilityId);
|
|
245
|
+
if (!operation) {
|
|
246
|
+
const label = params.kind === "queries" ? messages.query : messages.action;
|
|
247
|
+
const error = errorResponse(
|
|
248
|
+
"CAPABILITY_NOT_FOUND",
|
|
249
|
+
messages.notFound({ kind: label, reference: `${params.appId}.${params.capabilityId}` }),
|
|
250
|
+
404,
|
|
251
|
+
);
|
|
252
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
253
|
+
}
|
|
254
|
+
if (params.review && (params.kind !== "actions" || !("review" in operation) || operation.review !== true)) {
|
|
255
|
+
const error = errorResponse(
|
|
256
|
+
"CAPABILITY_NOT_FOUND",
|
|
257
|
+
messages.notFound({ kind: messages.review, reference: `${params.appId}.${params.capabilityId}` }),
|
|
258
|
+
404,
|
|
259
|
+
);
|
|
260
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const rawIdempotencyKey = params.request.headers.get("idempotency-key");
|
|
264
|
+
if (params.kind === "actions" && !params.review) {
|
|
265
|
+
const action = operation as (typeof entry.manifest.actions)[number];
|
|
266
|
+
if (action.idempotency === "required" && rawIdempotencyKey === null) {
|
|
267
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.idempotencyKeyRequired, messages.idempotencyRequired, 400);
|
|
268
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
269
|
+
}
|
|
270
|
+
if (action.idempotency === "none" && rawIdempotencyKey !== null) {
|
|
271
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.idempotencyKeyNotAllowed, messages.idempotencyNotAllowed, 400);
|
|
272
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
273
|
+
}
|
|
274
|
+
} else if (rawIdempotencyKey !== null) {
|
|
275
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.idempotencyKeyNotAllowed, messages.idempotencyOnlyActions, 400);
|
|
276
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
277
|
+
}
|
|
278
|
+
const idempotencyKey = rawIdempotencyKey === null ? null : CapabilityIdempotencyKeySchema.safeParse(rawIdempotencyKey);
|
|
279
|
+
if (idempotencyKey && !idempotencyKey.success) {
|
|
280
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.idempotencyInvalid, 400, {
|
|
281
|
+
issues: idempotencyKey.error.issues.map((issue) => ({ message: issue.message })),
|
|
282
|
+
});
|
|
283
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const inputValidator = schemaValidator(`${operation.schemaHash}:input`, operation.inputSchema);
|
|
287
|
+
if (!inputValidator) {
|
|
288
|
+
const invalid = errorResponse("INVALID_APP_RESPONSE", messages.unsupportedSchema({ appId: params.appId }), 502);
|
|
289
|
+
return capabilityJsonResponse(invalid.body, invalid.status);
|
|
290
|
+
}
|
|
291
|
+
const input = inputValidator.safeParse(params.input);
|
|
292
|
+
if (!input.success) {
|
|
293
|
+
const invalid = errorResponse("VALIDATION_FAILED", messages.inputSchemaMismatch, 400, {
|
|
294
|
+
issues: input.error.issues.slice(0, 20).map((issue) => ({ path: issue.path.join("."), message: issue.message })),
|
|
295
|
+
});
|
|
296
|
+
return capabilityJsonResponse(invalid.body, invalid.status);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let requestBody: string;
|
|
300
|
+
try {
|
|
301
|
+
requestBody = JSON.stringify({ input: input.data });
|
|
302
|
+
} catch {
|
|
303
|
+
const invalid = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.inputNotSerializable, 400);
|
|
304
|
+
return capabilityJsonResponse(invalid.body, invalid.status);
|
|
305
|
+
}
|
|
306
|
+
if (new TextEncoder().encode(requestBody).byteLength > CAPABILITY_MAX_REQUEST_BYTES) {
|
|
307
|
+
const invalid = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.requestTooLarge, 400);
|
|
308
|
+
return capabilityJsonResponse(invalid.body, invalid.status);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const invocationOperation = capabilityInvocationOperation(params.kind, params.capabilityId, params.review);
|
|
312
|
+
const mandate = params.mandate;
|
|
313
|
+
const timeoutMs =
|
|
314
|
+
params.kind === "queries" || params.review
|
|
315
|
+
? (params.dependencies?.queryTimeoutMs ?? QUERY_TIMEOUT_MS)
|
|
316
|
+
: (params.dependencies?.actionTimeoutMs ?? ACTION_TIMEOUT_MS);
|
|
317
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
318
|
+
const signal = AbortSignal.any([params.request.signal, timeout]);
|
|
319
|
+
let headers: Headers;
|
|
320
|
+
try {
|
|
321
|
+
headers = mandate
|
|
322
|
+
? await (async () => {
|
|
323
|
+
const issued = await waitWithin(
|
|
324
|
+
(params.dependencies?.withActiveSigner ?? withActiveIdentitySigner)(
|
|
325
|
+
"invocation",
|
|
326
|
+
(signer, db) =>
|
|
327
|
+
(params.dependencies?.withMandateIssueAuthority ?? withMandateIssueAuthority)(
|
|
328
|
+
{
|
|
329
|
+
mandateId: mandate.mandateId,
|
|
330
|
+
expectedRevision: mandate.mandateRevision,
|
|
331
|
+
ownerAppId: mandate.ownerAppId,
|
|
332
|
+
targetAppId: params.appId,
|
|
333
|
+
operation: invocationOperation,
|
|
334
|
+
actionApproval: mandate.actionApproval ?? "none",
|
|
335
|
+
requestId: normalizeInvocationRequestId(params.request.headers.get("x-request-id")),
|
|
336
|
+
},
|
|
337
|
+
async (mandateAuthority) => {
|
|
338
|
+
signal.throwIfAborted();
|
|
339
|
+
return (params.dependencies?.signInvocation ?? signInvocationToken)({
|
|
340
|
+
targetAppId: params.appId,
|
|
341
|
+
callingAppId: mandate.ownerAppId,
|
|
342
|
+
operation: invocationOperation,
|
|
343
|
+
schemaHash: operation.schemaHash,
|
|
344
|
+
authority: invocationAuthorityFromMandate(mandateAuthority),
|
|
345
|
+
requestId: normalizeInvocationRequestId(params.request.headers.get("x-request-id")),
|
|
346
|
+
signer,
|
|
347
|
+
issuer: signer.issuer,
|
|
348
|
+
});
|
|
349
|
+
},
|
|
350
|
+
{ db },
|
|
351
|
+
),
|
|
352
|
+
{ signal, timeoutMs },
|
|
353
|
+
),
|
|
354
|
+
signal,
|
|
355
|
+
);
|
|
356
|
+
if (!issued.ok) {
|
|
357
|
+
if (issued.error.code === "INTERNAL") throw new Error("Mandate invocation issuance failed");
|
|
358
|
+
throw new MandateDispatchError(issued.error.code === "CONFLICT" ? 409 : 403);
|
|
359
|
+
}
|
|
360
|
+
return new Headers({
|
|
361
|
+
"content-type": "application/json",
|
|
362
|
+
accept: "application/json",
|
|
363
|
+
authorization: `Bearer ${issued.data.token}`,
|
|
364
|
+
});
|
|
365
|
+
})()
|
|
366
|
+
: await (async () => {
|
|
367
|
+
if (!params.authority) throw new Error("Resolved request authority is required for Cloud invocation issuance");
|
|
368
|
+
const authority = params.authority;
|
|
369
|
+
const signed = await waitWithin(
|
|
370
|
+
(params.dependencies?.withActiveSigner ?? withActiveIdentitySigner)(
|
|
371
|
+
"invocation",
|
|
372
|
+
(signer) =>
|
|
373
|
+
(params.dependencies?.signInvocation ?? signInvocationToken)({
|
|
374
|
+
targetAppId: params.appId,
|
|
375
|
+
callingAppId: params.callingAppId ?? "core",
|
|
376
|
+
operation: invocationOperation,
|
|
377
|
+
schemaHash: operation.schemaHash,
|
|
378
|
+
authority: invocationAuthorityFromRequest(authority),
|
|
379
|
+
requestId: normalizeInvocationRequestId(params.request.headers.get("x-request-id")),
|
|
380
|
+
signer,
|
|
381
|
+
issuer: signer.issuer,
|
|
382
|
+
}),
|
|
383
|
+
{ signal, timeoutMs },
|
|
384
|
+
),
|
|
385
|
+
signal,
|
|
386
|
+
);
|
|
387
|
+
return new Headers({
|
|
388
|
+
"content-type": "application/json",
|
|
389
|
+
accept: "application/json",
|
|
390
|
+
authorization: `Bearer ${signed.token}`,
|
|
391
|
+
});
|
|
392
|
+
})();
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (error instanceof MandateDispatchError) {
|
|
395
|
+
const denied = errorResponse(
|
|
396
|
+
error.status === 409 ? "MANDATE_REVISION_CHANGED" : "MANDATE_FORBIDDEN",
|
|
397
|
+
error.status === 409 ? "Mandate revision changed" : "Mandate does not authorize this capability invocation",
|
|
398
|
+
error.status,
|
|
399
|
+
);
|
|
400
|
+
return capabilityJsonResponse(denied.body, denied.status);
|
|
401
|
+
}
|
|
402
|
+
if (params.request.signal.aborted || timeout.aborted) {
|
|
403
|
+
const cancelled = params.request.signal.aborted;
|
|
404
|
+
const failure = errorResponse(
|
|
405
|
+
cancelled ? CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled : CAPABILITY_FRAMEWORK_ERROR_CODES.deadlineExceeded,
|
|
406
|
+
cancelled ? messages.requestCancelled : messages.deadlineExceeded,
|
|
407
|
+
cancelled ? 499 : 504,
|
|
408
|
+
{ retrySafe: true },
|
|
409
|
+
);
|
|
410
|
+
return capabilityJsonResponse(failure.body, failure.status);
|
|
411
|
+
}
|
|
412
|
+
console.error("[capabilities] Invocation issuance failed", { appId: params.appId, operation: invocationOperation });
|
|
413
|
+
const failure = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.internal, messages.issuanceFailed, 500, { retrySafe: true });
|
|
414
|
+
return capabilityJsonResponse(failure.body, failure.status);
|
|
415
|
+
}
|
|
416
|
+
for (const name of ["x-request-id", "traceparent", "tracestate"] as const) {
|
|
417
|
+
const value =
|
|
418
|
+
name === "x-request-id" ? normalizeInvocationRequestId(params.request.headers.get(name)) : params.request.headers.get(name);
|
|
419
|
+
if (value) headers.set(name, value);
|
|
420
|
+
}
|
|
421
|
+
const invocationLocale = preferredLocale(params.request.headers);
|
|
422
|
+
if (invocationLocale) headers.set(LOCALE_HEADER, invocationLocale);
|
|
423
|
+
if (idempotencyKey?.success) headers.set("idempotency-key", idempotencyKey.data);
|
|
424
|
+
headers.set("x-cloud-capability-schema-hash", operation.schemaHash);
|
|
425
|
+
const actionWithoutRetrySafety =
|
|
426
|
+
params.kind === "actions" && !params.review && "idempotency" in operation && operation.idempotency === "none";
|
|
427
|
+
const outcomeUnknown = (): Response => {
|
|
428
|
+
const unknown = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.actionOutcomeUnknown, messages.actionOutcomeUnknown, 502, {
|
|
429
|
+
retrySafe: false,
|
|
430
|
+
});
|
|
431
|
+
return capabilityJsonResponse(unknown.body, unknown.status);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
let response: Response;
|
|
435
|
+
try {
|
|
436
|
+
response = await fetchUpstream(
|
|
437
|
+
`${entry.endpoint}/${params.kind}/${encodeURIComponent(params.capabilityId)}${params.review ? "/review" : ""}`,
|
|
438
|
+
{
|
|
439
|
+
method: "POST",
|
|
440
|
+
headers,
|
|
441
|
+
body: requestBody,
|
|
442
|
+
signal,
|
|
443
|
+
},
|
|
444
|
+
);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
log.warn("Capability app unavailable", {
|
|
447
|
+
appId: params.appId,
|
|
448
|
+
capabilityId: params.capabilityId,
|
|
449
|
+
kind: params.kind,
|
|
450
|
+
error: error instanceof Error ? error.message : String(error),
|
|
451
|
+
});
|
|
452
|
+
if (actionWithoutRetrySafety) return outcomeUnknown();
|
|
453
|
+
if (params.request.signal.aborted) {
|
|
454
|
+
const cancelled = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled, messages.requestCancelled, 499, {
|
|
455
|
+
retrySafe: params.kind === "queries" || ("idempotency" in operation && operation.idempotency === "required"),
|
|
456
|
+
});
|
|
457
|
+
return capabilityJsonResponse(cancelled.body, cancelled.status);
|
|
458
|
+
}
|
|
459
|
+
if (timeout.aborted) {
|
|
460
|
+
const deadline = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.deadlineExceeded, messages.deadlineExceeded, 504, {
|
|
461
|
+
retrySafe: params.kind === "queries" || ("idempotency" in operation && operation.idempotency === "required"),
|
|
462
|
+
});
|
|
463
|
+
return capabilityJsonResponse(deadline.body, deadline.status);
|
|
464
|
+
}
|
|
465
|
+
const unavailable = errorResponse(
|
|
466
|
+
CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable,
|
|
467
|
+
messages.appCouldNotServe({ appId: params.appId }),
|
|
468
|
+
503,
|
|
469
|
+
{ retrySafe: params.kind === "queries" || ("idempotency" in operation && operation.idempotency === "required") },
|
|
470
|
+
);
|
|
471
|
+
return capabilityJsonResponse(unavailable.body, unavailable.status);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const upstreamBody = await readBoundedJson(response, CAPABILITY_MAX_RESULT_BYTES);
|
|
475
|
+
if (!upstreamBody.ok) {
|
|
476
|
+
if (actionWithoutRetrySafety) return outcomeUnknown();
|
|
477
|
+
const code =
|
|
478
|
+
upstreamBody.reason === "too_large"
|
|
479
|
+
? CAPABILITY_FRAMEWORK_ERROR_CODES.responseTooLarge
|
|
480
|
+
: CAPABILITY_FRAMEWORK_ERROR_CODES.invalidAppResponse;
|
|
481
|
+
const invalid = errorResponse(code, messages.invalidCapabilityJson({ appId: params.appId }), 502);
|
|
482
|
+
return capabilityJsonResponse(invalid.body, invalid.status);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (!response.ok) {
|
|
486
|
+
const parsed = CapabilityErrorSchema.safeParse(upstreamBody.data);
|
|
487
|
+
if (parsed.success && response.status >= 400 && response.status <= 599) {
|
|
488
|
+
return capabilityJsonResponse(parsed.data, response.status);
|
|
489
|
+
}
|
|
490
|
+
if (actionWithoutRetrySafety) return outcomeUnknown();
|
|
491
|
+
const invalid = errorResponse("INVALID_APP_RESPONSE", messages.invalidCapabilityError({ appId: params.appId }), 502);
|
|
492
|
+
return capabilityJsonResponse(invalid.body, invalid.status);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const resultValidator = params.review
|
|
496
|
+
? CapabilityActionReviewSchema
|
|
497
|
+
: schemaValidator(`${operation.schemaHash}:result`, capabilityResultJsonSchema(operation.dataSchema));
|
|
498
|
+
const parsedResult = resultValidator?.safeParse(upstreamBody.data);
|
|
499
|
+
const parsedReview = params.review ? CapabilityActionReviewSchema.safeParse(upstreamBody.data) : null;
|
|
500
|
+
const reviewApprovalScopeIsValid =
|
|
501
|
+
!params.review ||
|
|
502
|
+
!("approval" in operation) ||
|
|
503
|
+
!parsedReview?.success ||
|
|
504
|
+
(operation.approval === "rememberable" ? parsedReview.data.approvalScope !== undefined : parsedReview.data.approvalScope === undefined);
|
|
505
|
+
if (!resultValidator || !parsedResult?.success || !reviewApprovalScopeIsValid) {
|
|
506
|
+
if (actionWithoutRetrySafety) return outcomeUnknown();
|
|
507
|
+
const invalid = errorResponse("INVALID_APP_RESPONSE", messages.outsideResultSchema({ appId: params.appId }), 502);
|
|
508
|
+
return capabilityJsonResponse(invalid.body, invalid.status);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return capabilityJsonResponse(upstreamBody.data, 200);
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
class MandateDispatchError extends Error {
|
|
515
|
+
constructor(readonly status: 403 | 409) {
|
|
516
|
+
super("Mandate does not authorize this capability invocation");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export const createCapabilityRoutes = (dependencies: CapabilityRouteDependencies = {}) => {
|
|
521
|
+
const requireReadScope = auth.requireOAuthScope("read", "admin");
|
|
522
|
+
const requireWriteScope = auth.requireOAuthScope("write", "admin");
|
|
523
|
+
const requireInvocationScope: MiddlewareHandler<AuthContext> = (c, next) =>
|
|
524
|
+
(c.req.param("kind") === "actions" ? requireWriteScope : requireReadScope)(c, next);
|
|
525
|
+
|
|
526
|
+
return new Hono<AuthContext>()
|
|
527
|
+
.use("/capabilities/v1/*", dependencies.authenticate ?? auth.requireRole("authenticated"))
|
|
528
|
+
.use("/capabilities/v1/*", rejectReservedWorkloadCredential)
|
|
529
|
+
.get(
|
|
530
|
+
"/capabilities/v1/catalog",
|
|
531
|
+
describeRoute({
|
|
532
|
+
tags: ["Capabilities"],
|
|
533
|
+
summary: "List live app capabilities",
|
|
534
|
+
description: "Returns the versioned capability manifests currently advertised by live app leases.",
|
|
535
|
+
...requiresAuth,
|
|
536
|
+
responses: {
|
|
537
|
+
200: jsonResponse(CapabilityCatalogSchema, "Live capability catalog"),
|
|
538
|
+
401: jsonResponse(CapabilityErrorSchema, "Authentication required"),
|
|
539
|
+
403: jsonResponse(CapabilityErrorSchema, "Insufficient OAuth scope"),
|
|
540
|
+
503: jsonResponse(CapabilityErrorSchema, "Capability registry unavailable"),
|
|
541
|
+
},
|
|
542
|
+
}),
|
|
543
|
+
requireReadScope,
|
|
544
|
+
v("query", CapabilityCatalogQuerySchema),
|
|
545
|
+
async (c) => {
|
|
546
|
+
const query = c.req.valid("query");
|
|
547
|
+
const messages = capabilityMessages(getLocale(c));
|
|
548
|
+
try {
|
|
549
|
+
return c.json(await loadCapabilityCatalogPage(query, dependencies, getLocale(c)));
|
|
550
|
+
} catch (error) {
|
|
551
|
+
log.warn("Capability catalog unavailable", { error: error instanceof Error ? error.message : String(error) });
|
|
552
|
+
const unavailable = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable, messages.registryUnavailable, 503);
|
|
553
|
+
return capabilityJsonResponse(unavailable.body, unavailable.status);
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
)
|
|
557
|
+
.post(
|
|
558
|
+
"/capabilities/v1/actions/:appId/:capabilityId/review",
|
|
559
|
+
describeRoute({
|
|
560
|
+
tags: ["Capabilities"],
|
|
561
|
+
summary: "Review one live app Action",
|
|
562
|
+
description: "Resolves the optional read-only human review for an Action with the caller credential.",
|
|
563
|
+
...requiresAuth,
|
|
564
|
+
responses: {
|
|
565
|
+
200: jsonResponse(z.unknown(), "Capability Action review"),
|
|
566
|
+
400: jsonResponse(CapabilityErrorSchema, "Invalid request"),
|
|
567
|
+
401: jsonResponse(CapabilityErrorSchema, "Authentication required"),
|
|
568
|
+
403: jsonResponse(CapabilityErrorSchema, "Insufficient OAuth scope"),
|
|
569
|
+
404: jsonResponse(CapabilityErrorSchema, "Capability review not found"),
|
|
570
|
+
409: jsonResponse(CapabilityErrorSchema, "Schema changed"),
|
|
571
|
+
499: jsonResponse(CapabilityErrorSchema, "Request cancelled"),
|
|
572
|
+
500: jsonResponse(CapabilityErrorSchema, "Capability review failed"),
|
|
573
|
+
502: jsonResponse(CapabilityErrorSchema, "Invalid app response"),
|
|
574
|
+
503: jsonResponse(CapabilityErrorSchema, "App unavailable"),
|
|
575
|
+
504: jsonResponse(CapabilityErrorSchema, "App deadline exceeded"),
|
|
576
|
+
},
|
|
577
|
+
}),
|
|
578
|
+
requireReadScope,
|
|
579
|
+
async (c) => {
|
|
580
|
+
const locale = getLocale(c);
|
|
581
|
+
const messages = capabilityMessages(locale);
|
|
582
|
+
const body = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
|
|
583
|
+
if (!body.ok) {
|
|
584
|
+
const message = body.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
|
|
585
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message, 400);
|
|
586
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
587
|
+
}
|
|
588
|
+
const request = CapabilityInvocationRequestSchema.safeParse(body.data);
|
|
589
|
+
if (!request.success) {
|
|
590
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.requestInputOnly, 400);
|
|
591
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
592
|
+
}
|
|
593
|
+
return dispatchCapability({
|
|
594
|
+
request: c.req.raw,
|
|
595
|
+
kind: "actions",
|
|
596
|
+
review: true,
|
|
597
|
+
appId: c.req.param("appId") ?? "",
|
|
598
|
+
capabilityId: c.req.param("capabilityId") ?? "",
|
|
599
|
+
input: request.data.input,
|
|
600
|
+
authority: auth.getAuthority(c),
|
|
601
|
+
locale,
|
|
602
|
+
dependencies,
|
|
603
|
+
});
|
|
604
|
+
},
|
|
605
|
+
)
|
|
606
|
+
.post(
|
|
607
|
+
"/capabilities/v1/:kind{queries|actions}/:appId/:capabilityId",
|
|
608
|
+
describeRoute({
|
|
609
|
+
tags: ["Capabilities"],
|
|
610
|
+
summary: "Invoke one live app capability",
|
|
611
|
+
description: "Validates the live manifest and dispatches to the framework-owned app endpoint with the caller credential.",
|
|
612
|
+
...requiresAuth,
|
|
613
|
+
responses: {
|
|
614
|
+
200: jsonResponse(z.unknown(), "Capability result"),
|
|
615
|
+
400: jsonResponse(CapabilityErrorSchema, "Invalid request"),
|
|
616
|
+
401: jsonResponse(CapabilityErrorSchema, "Authentication required"),
|
|
617
|
+
403: jsonResponse(CapabilityErrorSchema, "Insufficient OAuth scope"),
|
|
618
|
+
404: jsonResponse(CapabilityErrorSchema, "Capability not found"),
|
|
619
|
+
409: jsonResponse(CapabilityErrorSchema, "Schema changed"),
|
|
620
|
+
429: jsonResponse(CapabilityErrorSchema, "Capability rate limited"),
|
|
621
|
+
499: jsonResponse(CapabilityErrorSchema, "Request cancelled"),
|
|
622
|
+
500: jsonResponse(CapabilityErrorSchema, "Capability execution failed"),
|
|
623
|
+
502: jsonResponse(CapabilityErrorSchema, "Invalid app response"),
|
|
624
|
+
503: jsonResponse(CapabilityErrorSchema, "App unavailable"),
|
|
625
|
+
504: jsonResponse(CapabilityErrorSchema, "App deadline exceeded"),
|
|
626
|
+
},
|
|
627
|
+
}),
|
|
628
|
+
requireInvocationScope,
|
|
629
|
+
async (c) => {
|
|
630
|
+
const locale = getLocale(c);
|
|
631
|
+
const messages = capabilityMessages(locale);
|
|
632
|
+
const body = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
|
|
633
|
+
if (!body.ok) {
|
|
634
|
+
const message = body.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
|
|
635
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message, 400);
|
|
636
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
637
|
+
}
|
|
638
|
+
const request = CapabilityInvocationRequestSchema.safeParse(body.data);
|
|
639
|
+
if (!request.success) {
|
|
640
|
+
const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.requestInputOnly, 400);
|
|
641
|
+
return capabilityJsonResponse(error.body, error.status);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return dispatchCapability({
|
|
645
|
+
request: c.req.raw,
|
|
646
|
+
kind: c.req.param("kind") as "queries" | "actions",
|
|
647
|
+
appId: c.req.param("appId") ?? "",
|
|
648
|
+
capabilityId: c.req.param("capabilityId") ?? "",
|
|
649
|
+
input: request.data.input,
|
|
650
|
+
authority: auth.getAuthority(c),
|
|
651
|
+
locale,
|
|
652
|
+
dependencies,
|
|
653
|
+
});
|
|
654
|
+
},
|
|
655
|
+
);
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
export type CapabilityApiType = ReturnType<typeof createCapabilityRoutes>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { i18n } from "@k2b/stdlib";
|
|
2
|
+
|
|
3
|
+
export const helpApiMessages = i18n.define({
|
|
4
|
+
baseLocale: "en",
|
|
5
|
+
messages: {
|
|
6
|
+
en: {
|
|
7
|
+
unavailable: ({ appId }: { appId: string }) => `Help for ${appId} is not currently available`,
|
|
8
|
+
stale: ({ appId }: { appId: string }) => `Help for ${appId} is being refreshed`,
|
|
9
|
+
notFound: "Help document not found",
|
|
10
|
+
},
|
|
11
|
+
de: {
|
|
12
|
+
unavailable: ({ appId }) => `Die Hilfe für ${appId} ist derzeit nicht verfügbar`,
|
|
13
|
+
stale: ({ appId }) => `Die Hilfe für ${appId} wird gerade aktualisiert`,
|
|
14
|
+
notFound: "Hilfedokument nicht gefunden",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type HelpApiMessages = ReturnType<typeof helpApiMessages.resolve>["t"];
|