@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,76 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { sql } from "bun";
|
|
3
|
+
import type { Context } from "hono";
|
|
4
|
+
import { HTTPException } from "hono/http-exception";
|
|
5
|
+
import { isAccountCategoryAllowed } from "./account-category-policy";
|
|
6
|
+
import { isAccountExpired } from "./account-model";
|
|
7
|
+
import { verifySessionToken } from "./identity";
|
|
8
|
+
import { getIdentityRuntimeConfig } from "./identity/runtime-config";
|
|
9
|
+
import { session } from "./session";
|
|
10
|
+
import { loadJwtSessionUser } from "./session/user";
|
|
11
|
+
import * as settings from "./settings";
|
|
12
|
+
|
|
13
|
+
/** First-use acceptance, not an automatic re-consent policy for later edits. */
|
|
14
|
+
export const legalConsent = {
|
|
15
|
+
documents: async () => {
|
|
16
|
+
const document = async (kind: "terms" | "privacy") => {
|
|
17
|
+
const [mode, url, content] = await Promise.all([
|
|
18
|
+
settings.get<string>(`legal.${kind}.mode`),
|
|
19
|
+
settings.get<string>(`legal.${kind}.url`),
|
|
20
|
+
settings.get<string>(`legal.${kind}.content`),
|
|
21
|
+
]);
|
|
22
|
+
return mode === "external" && url?.trim()
|
|
23
|
+
? { kind, mode: "external", value: url.trim() }
|
|
24
|
+
: { kind, mode: "local", value: (content ?? "").trim() };
|
|
25
|
+
};
|
|
26
|
+
const documents = await Promise.all([document("terms"), document("privacy")]);
|
|
27
|
+
const version = createHash("sha256").update(JSON.stringify(documents)).digest("hex");
|
|
28
|
+
return { documents, version };
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// Deliberately separate from ordinary session authentication. Callers must
|
|
32
|
+
// never use this identity to authorize application data or issue credentials.
|
|
33
|
+
pending: async (c: Context) => {
|
|
34
|
+
const token = session.getToken(c);
|
|
35
|
+
const claims = token ? await verifySessionToken(token) : null;
|
|
36
|
+
if (!claims) return null;
|
|
37
|
+
const { groupsAdmin } = await getIdentityRuntimeConfig();
|
|
38
|
+
const user = await loadJwtSessionUser({
|
|
39
|
+
userId: claims.sub,
|
|
40
|
+
sid: claims.sid,
|
|
41
|
+
authEpoch: claims.auth_epoch,
|
|
42
|
+
groupsAdmin,
|
|
43
|
+
allowPendingLegalConsent: true,
|
|
44
|
+
});
|
|
45
|
+
if (!user || isAccountExpired(user.accountExpires) || !(await isAccountCategoryAllowed(user))) return null;
|
|
46
|
+
const [row] = await sql<{ pending: boolean }[]>`
|
|
47
|
+
SELECT legal_pending AND NOT EXISTS (SELECT 1 FROM auth.legal_acceptances WHERE user_id = ${user.id}::uuid) AS pending
|
|
48
|
+
FROM auth.session_families WHERE sid = ${claims.sid}::uuid
|
|
49
|
+
`;
|
|
50
|
+
return row?.pending ? { user, sid: claims.sid, authEpoch: claims.auth_epoch } : null;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
accept: async (c: Context, version: string) => {
|
|
54
|
+
const pending = await legalConsent.pending(c);
|
|
55
|
+
if (!pending) throw new HTTPException(401, { message: "Sign in again to continue." });
|
|
56
|
+
const current = await legalConsent.documents();
|
|
57
|
+
if (current.version !== version) throw new HTTPException(409, { message: "The documents changed. Reload and review them again." });
|
|
58
|
+
await sql.begin(async (tx) => {
|
|
59
|
+
const [user] = await tx<{ provider: "local" | "ipa"; profile: "guest" | "user" }[]>`
|
|
60
|
+
SELECT u.provider, u.profile FROM auth.users u JOIN auth.session_families sf ON sf.user_id = u.id
|
|
61
|
+
JOIN auth.signing_keys sk ON sk.kid = sf.signing_kid AND sk.state <> 'revoked'
|
|
62
|
+
WHERE u.id = ${pending.user.id}::uuid AND sf.sid = ${pending.sid}::uuid
|
|
63
|
+
AND u.auth_epoch = ${pending.authEpoch} AND sf.auth_epoch = u.auth_epoch
|
|
64
|
+
AND sf.revoked_at IS NULL AND sf.expires_at > now()
|
|
65
|
+
AND (u.account_expires IS NULL OR u.account_expires > now())
|
|
66
|
+
FOR UPDATE OF u, sf
|
|
67
|
+
`;
|
|
68
|
+
if (!user || !(await isAccountCategoryAllowed(user, tx))) throw new HTTPException(401, { message: "Sign in again to continue." });
|
|
69
|
+
await tx`
|
|
70
|
+
INSERT INTO auth.legal_acceptances (user_id, session_id, document_version, documents)
|
|
71
|
+
VALUES (${pending.user.id}::uuid, ${pending.sid}::uuid, ${current.version}, ${JSON.stringify(current.documents)}::text::jsonb)
|
|
72
|
+
ON CONFLICT (user_id) DO NOTHING
|
|
73
|
+
`;
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import type { PaginationParams } from "../../contracts/shared";
|
|
3
|
+
import { escapeLikePattern, parsePgJsonRecord, toPgTextArray } from "../postgres";
|
|
4
|
+
import { registerSettings } from "../settings/defaults";
|
|
5
|
+
import { redactMetadata } from "./redaction";
|
|
6
|
+
import { observeSyncEvent, TRACE_STUCK_AFTER_MS, trace } from "./trace";
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
TraceAttributes,
|
|
10
|
+
TraceAttributeValue,
|
|
11
|
+
TraceCategory,
|
|
12
|
+
TraceContext,
|
|
13
|
+
TraceEvent,
|
|
14
|
+
TraceListFilter,
|
|
15
|
+
TraceRunStats,
|
|
16
|
+
TraceSeverity,
|
|
17
|
+
TraceSourceGroup,
|
|
18
|
+
TraceSpan,
|
|
19
|
+
TraceSpanKind,
|
|
20
|
+
TraceStatus,
|
|
21
|
+
TraceSummary,
|
|
22
|
+
TraceWindow,
|
|
23
|
+
} from "./trace";
|
|
24
|
+
export { observeSyncEvent, TRACE_STUCK_AFTER_MS, trace };
|
|
25
|
+
|
|
26
|
+
// ── Settings Registration ──────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
registerSettings([
|
|
29
|
+
{
|
|
30
|
+
key: "logs.retention_days",
|
|
31
|
+
kind: "number",
|
|
32
|
+
default: 30,
|
|
33
|
+
description: "Automatically delete log entries older than this many days",
|
|
34
|
+
group: "logs",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: "logs.trace_retention_days",
|
|
38
|
+
kind: "number",
|
|
39
|
+
default: 30,
|
|
40
|
+
description: "Automatically delete completed traces older than this many days",
|
|
41
|
+
group: "logs",
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
// ==========================
|
|
46
|
+
// Types
|
|
47
|
+
// ==========================
|
|
48
|
+
|
|
49
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
50
|
+
|
|
51
|
+
type WriteParams = {
|
|
52
|
+
level: LogLevel;
|
|
53
|
+
source: string;
|
|
54
|
+
message: string;
|
|
55
|
+
metadata?: Record<string, unknown>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type Logger = {
|
|
59
|
+
debug: (message: string, metadata?: Record<string, unknown>) => void;
|
|
60
|
+
info: (message: string, metadata?: Record<string, unknown>) => void;
|
|
61
|
+
warn: (message: string, metadata?: Record<string, unknown>) => void;
|
|
62
|
+
error: (message: string, metadata?: Record<string, unknown>) => void;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type LogEntry = {
|
|
66
|
+
id: string;
|
|
67
|
+
level: LogLevel;
|
|
68
|
+
source: string;
|
|
69
|
+
message: string;
|
|
70
|
+
metadata: Record<string, unknown> | null;
|
|
71
|
+
createdAt: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
type DbLogRow = {
|
|
75
|
+
id: string | number;
|
|
76
|
+
level: string;
|
|
77
|
+
source: string;
|
|
78
|
+
message: string;
|
|
79
|
+
metadata: Record<string, unknown> | null;
|
|
80
|
+
created_at: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type LogStatsGroupBy = "source" | "level";
|
|
84
|
+
|
|
85
|
+
export type LogStatsRow = {
|
|
86
|
+
key: string;
|
|
87
|
+
count: number;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type LogTimeseriesPoint = {
|
|
91
|
+
at: Date;
|
|
92
|
+
debug: number;
|
|
93
|
+
info: number;
|
|
94
|
+
warn: number;
|
|
95
|
+
error: number;
|
|
96
|
+
total: number;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ==========================
|
|
100
|
+
// Helpers
|
|
101
|
+
// ==========================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Maps one logging row to the public log entry shape.
|
|
105
|
+
*/
|
|
106
|
+
const mapRow = (row: DbLogRow): LogEntry => ({
|
|
107
|
+
id: String(row.id),
|
|
108
|
+
level: row.level as LogLevel,
|
|
109
|
+
source: row.source,
|
|
110
|
+
message: row.message,
|
|
111
|
+
metadata: parsePgJsonRecord(row.metadata),
|
|
112
|
+
createdAt: row.created_at,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ==========================
|
|
116
|
+
// Core: Write + Logger
|
|
117
|
+
// ==========================
|
|
118
|
+
|
|
119
|
+
/** Fire-and-forget write — mirrors to console, then inserts to DB async. */
|
|
120
|
+
function write(params: WriteParams): void {
|
|
121
|
+
const safeMetadata = params.metadata ? (redactMetadata(params.metadata) as Record<string, unknown>) : undefined;
|
|
122
|
+
const prefix = `[${params.source}]`;
|
|
123
|
+
const consoleFn = params.level === "error" ? console.error : params.level === "warn" ? console.warn : console.log;
|
|
124
|
+
consoleFn(prefix, params.message, ...(safeMetadata ? [safeMetadata] : []));
|
|
125
|
+
|
|
126
|
+
sql`
|
|
127
|
+
INSERT INTO logging.entries (level, source, message, metadata)
|
|
128
|
+
VALUES (
|
|
129
|
+
${params.level},
|
|
130
|
+
${params.source},
|
|
131
|
+
${params.message},
|
|
132
|
+
${safeMetadata ? JSON.stringify(safeMetadata) : null}::jsonb
|
|
133
|
+
)
|
|
134
|
+
`.catch((err: Error) => console.error("[logging] DB write failed:", err.message));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Stateless logger factory. Creates an object with .debug/.info/.warn/.error methods
|
|
139
|
+
* bound to the given source. Can be called inline or cached — no state is held.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* // Inline
|
|
143
|
+
* logger("yjs").info("Document loaded", { noteId });
|
|
144
|
+
*
|
|
145
|
+
* // Cached
|
|
146
|
+
* const log = logger("weather");
|
|
147
|
+
* log.error("API error", { status: 500 });
|
|
148
|
+
*/
|
|
149
|
+
export function logger(source: string): Logger {
|
|
150
|
+
return {
|
|
151
|
+
debug: (msg, meta) => write({ level: "debug", source, message: msg, metadata: meta }),
|
|
152
|
+
info: (msg, meta) => write({ level: "info", source, message: msg, metadata: meta }),
|
|
153
|
+
warn: (msg, meta) => write({ level: "warn", source, message: msg, metadata: meta }),
|
|
154
|
+
error: (msg, meta) => write({ level: "error", source, message: msg, metadata: meta }),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ==========================
|
|
159
|
+
// Admin: List / Sources / Cleanup
|
|
160
|
+
// ==========================
|
|
161
|
+
|
|
162
|
+
/** List log entries with pagination and optional filters. */
|
|
163
|
+
const list = async (
|
|
164
|
+
pagination: PaginationParams,
|
|
165
|
+
options?: {
|
|
166
|
+
source?: string;
|
|
167
|
+
sources?: string[];
|
|
168
|
+
level?: string;
|
|
169
|
+
search?: string;
|
|
170
|
+
sinceHours?: number;
|
|
171
|
+
},
|
|
172
|
+
): Promise<{ entries: LogEntry[]; total: number }> => {
|
|
173
|
+
const { offset, perPage } = pagination;
|
|
174
|
+
const { source, sources, level, search, sinceHours } = options ?? {};
|
|
175
|
+
|
|
176
|
+
const searchPattern = search ? `%${escapeLikePattern(search)}%` : null;
|
|
177
|
+
const filterSource = source && source !== "all" ? source : null;
|
|
178
|
+
const filterSources =
|
|
179
|
+
!filterSource && Array.isArray(sources) && sources.length > 0
|
|
180
|
+
? [...new Set(sources.map((value) => value.trim()).filter(Boolean))]
|
|
181
|
+
: null;
|
|
182
|
+
const filterSourcesLiteral = filterSources ? toPgTextArray(filterSources) : null;
|
|
183
|
+
const filterLevel = level && level !== "all" ? level : null;
|
|
184
|
+
const hasSourceList = !!filterSources && filterSources.length > 0;
|
|
185
|
+
const filterSinceHours = Number.isFinite(sinceHours) && sinceHours && sinceHours > 0 ? Math.trunc(sinceHours) : null;
|
|
186
|
+
|
|
187
|
+
let countRows: Array<{ count: number }> = [];
|
|
188
|
+
let dataRows: DbLogRow[] = [];
|
|
189
|
+
|
|
190
|
+
if (filterSource) {
|
|
191
|
+
countRows = await sql`
|
|
192
|
+
SELECT COUNT(*)::int as count
|
|
193
|
+
FROM logging.entries
|
|
194
|
+
WHERE source = ${filterSource}
|
|
195
|
+
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
196
|
+
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
197
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
dataRows = await sql`
|
|
201
|
+
SELECT id, level, source, message, metadata, created_at
|
|
202
|
+
FROM logging.entries
|
|
203
|
+
WHERE source = ${filterSource}
|
|
204
|
+
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
205
|
+
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
206
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
|
|
207
|
+
ORDER BY created_at DESC
|
|
208
|
+
LIMIT ${perPage} OFFSET ${offset}
|
|
209
|
+
`;
|
|
210
|
+
} else if (hasSourceList) {
|
|
211
|
+
countRows = await sql`
|
|
212
|
+
SELECT COUNT(*)::int as count
|
|
213
|
+
FROM logging.entries
|
|
214
|
+
WHERE source = ANY(${filterSourcesLiteral}::text[])
|
|
215
|
+
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
216
|
+
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
217
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
dataRows = await sql`
|
|
221
|
+
SELECT id, level, source, message, metadata, created_at
|
|
222
|
+
FROM logging.entries
|
|
223
|
+
WHERE source = ANY(${filterSourcesLiteral}::text[])
|
|
224
|
+
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
225
|
+
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
226
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
|
|
227
|
+
ORDER BY created_at DESC
|
|
228
|
+
LIMIT ${perPage} OFFSET ${offset}
|
|
229
|
+
`;
|
|
230
|
+
} else {
|
|
231
|
+
countRows = await sql`
|
|
232
|
+
SELECT COUNT(*)::int as count
|
|
233
|
+
FROM logging.entries
|
|
234
|
+
WHERE (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
235
|
+
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
236
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
|
|
237
|
+
`;
|
|
238
|
+
|
|
239
|
+
dataRows = await sql`
|
|
240
|
+
SELECT id, level, source, message, metadata, created_at
|
|
241
|
+
FROM logging.entries
|
|
242
|
+
WHERE (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
243
|
+
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
244
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
|
|
245
|
+
ORDER BY created_at DESC
|
|
246
|
+
LIMIT ${perPage} OFFSET ${offset}
|
|
247
|
+
`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
entries: dataRows.map((row: DbLogRow) => mapRow(row)),
|
|
252
|
+
total: countRows[0]?.count ?? 0,
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/** Get all unique source names (for filter dropdown). */
|
|
257
|
+
const getSources = async (): Promise<string[]> => {
|
|
258
|
+
const rows = await sql`
|
|
259
|
+
SELECT DISTINCT source FROM logging.entries ORDER BY source
|
|
260
|
+
`;
|
|
261
|
+
return rows.map((row: { source: string }) => row.source);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const normalizeSinceHours = (sinceHours: number | undefined): number | null =>
|
|
265
|
+
Number.isFinite(sinceHours) && sinceHours && sinceHours > 0 ? Math.trunc(sinceHours) : null;
|
|
266
|
+
|
|
267
|
+
const normalizeStatsLimit = (limit: number | undefined): number => Math.min(Math.max(Math.trunc(limit ?? 50), 1), 200);
|
|
268
|
+
|
|
269
|
+
const logBucketMinutes = (sinceHours: number): number => {
|
|
270
|
+
if (sinceHours <= 1) return 5;
|
|
271
|
+
if (sinceHours <= 24) return 60;
|
|
272
|
+
if (sinceHours <= 168) return 360;
|
|
273
|
+
return 1_440;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Gap-filled log volume by level for operator charts.
|
|
278
|
+
*
|
|
279
|
+
* The lookback is capped at 30 days and the bucket ladder keeps the response
|
|
280
|
+
* between 12 and 31 points for every window exposed by Gateway Ops.
|
|
281
|
+
*/
|
|
282
|
+
const timeseries = async (
|
|
283
|
+
options: { source?: string; sources?: string[]; level?: string; search?: string; sinceHours?: number } = {},
|
|
284
|
+
): Promise<LogTimeseriesPoint[]> => {
|
|
285
|
+
const sinceHours = Math.min(720, Math.max(1, normalizeSinceHours(options.sinceHours) ?? 24));
|
|
286
|
+
const bucketMinutes = logBucketMinutes(sinceHours);
|
|
287
|
+
const requestedSources = options.source
|
|
288
|
+
? [options.source]
|
|
289
|
+
: [...new Set((options.sources ?? []).map((value) => value.trim()).filter(Boolean))].slice(0, 100);
|
|
290
|
+
const hasSources = requestedSources.length > 0;
|
|
291
|
+
const sourceLiteral = toPgTextArray(requestedSources);
|
|
292
|
+
const level = options.level && options.level !== "all" ? options.level : null;
|
|
293
|
+
const search = options.search?.trim().slice(0, 200);
|
|
294
|
+
const searchPattern = search ? `%${escapeLikePattern(search)}%` : null;
|
|
295
|
+
|
|
296
|
+
return sql<LogTimeseriesPoint[]>`
|
|
297
|
+
WITH bounds AS (
|
|
298
|
+
SELECT
|
|
299
|
+
now() AS end_at,
|
|
300
|
+
now() - (${sinceHours}::int * INTERVAL '1 hour') AS start_at,
|
|
301
|
+
${bucketMinutes}::int * INTERVAL '1 minute' AS bucket
|
|
302
|
+
),
|
|
303
|
+
buckets AS (
|
|
304
|
+
SELECT generate_series(
|
|
305
|
+
date_bin(bucket, start_at, TIMESTAMPTZ '1970-01-01 00:00:00+00'),
|
|
306
|
+
date_bin(bucket, end_at, TIMESTAMPTZ '1970-01-01 00:00:00+00'),
|
|
307
|
+
bucket
|
|
308
|
+
) AS at
|
|
309
|
+
FROM bounds
|
|
310
|
+
),
|
|
311
|
+
counts AS (
|
|
312
|
+
SELECT
|
|
313
|
+
date_bin(bounds.bucket, entries.created_at, TIMESTAMPTZ '1970-01-01 00:00:00+00') AS at,
|
|
314
|
+
entries.level,
|
|
315
|
+
COUNT(*)::int AS count
|
|
316
|
+
FROM logging.entries AS entries
|
|
317
|
+
CROSS JOIN bounds
|
|
318
|
+
WHERE entries.created_at >= bounds.start_at
|
|
319
|
+
AND entries.created_at <= bounds.end_at
|
|
320
|
+
AND (${hasSources}::boolean = false OR entries.source = ANY(${sourceLiteral}::text[]))
|
|
321
|
+
AND (${level}::text IS NULL OR entries.level = ${level})
|
|
322
|
+
AND (
|
|
323
|
+
${searchPattern}::text IS NULL
|
|
324
|
+
OR entries.message ILIKE ${searchPattern} ESCAPE '\'
|
|
325
|
+
OR entries.metadata::text ILIKE ${searchPattern} ESCAPE '\'
|
|
326
|
+
)
|
|
327
|
+
GROUP BY at, entries.level
|
|
328
|
+
)
|
|
329
|
+
SELECT
|
|
330
|
+
buckets.at,
|
|
331
|
+
COALESCE(SUM(counts.count) FILTER (WHERE counts.level = 'debug'), 0)::int AS debug,
|
|
332
|
+
COALESCE(SUM(counts.count) FILTER (WHERE counts.level = 'info'), 0)::int AS info,
|
|
333
|
+
COALESCE(SUM(counts.count) FILTER (WHERE counts.level = 'warn'), 0)::int AS warn,
|
|
334
|
+
COALESCE(SUM(counts.count) FILTER (WHERE counts.level = 'error'), 0)::int AS error,
|
|
335
|
+
COALESCE(SUM(counts.count), 0)::int AS total
|
|
336
|
+
FROM buckets
|
|
337
|
+
LEFT JOIN counts USING (at)
|
|
338
|
+
GROUP BY buckets.at
|
|
339
|
+
ORDER BY buckets.at
|
|
340
|
+
`;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
/** Group log volume by source or level for admin diagnostics. */
|
|
344
|
+
const statsBy = async (
|
|
345
|
+
groupBy: LogStatsGroupBy,
|
|
346
|
+
options?: {
|
|
347
|
+
sinceHours?: number;
|
|
348
|
+
limit?: number;
|
|
349
|
+
sources?: string[];
|
|
350
|
+
level?: string;
|
|
351
|
+
search?: string;
|
|
352
|
+
},
|
|
353
|
+
): Promise<LogStatsRow[]> => {
|
|
354
|
+
const filterSinceHours = normalizeSinceHours(options?.sinceHours);
|
|
355
|
+
const limit = normalizeStatsLimit(options?.limit);
|
|
356
|
+
const sources = [...new Set((options?.sources ?? []).map((value) => value.trim()).filter(Boolean))].slice(0, 100);
|
|
357
|
+
const hasSources = sources.length > 0;
|
|
358
|
+
const sourceLiteral = toPgTextArray(sources);
|
|
359
|
+
const level = options?.level && options.level !== "all" ? options.level : null;
|
|
360
|
+
const search = options?.search?.trim().slice(0, 200);
|
|
361
|
+
const searchPattern = search ? `%${escapeLikePattern(search)}%` : null;
|
|
362
|
+
|
|
363
|
+
if (groupBy === "level") {
|
|
364
|
+
const rows = await sql<{ key: string; count: number }[]>`
|
|
365
|
+
SELECT level as key, COUNT(*)::int as count
|
|
366
|
+
FROM logging.entries
|
|
367
|
+
WHERE (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
368
|
+
AND (${hasSources}::boolean = false OR source = ANY(${sourceLiteral}::text[]))
|
|
369
|
+
AND (${level}::text IS NULL OR level = ${level})
|
|
370
|
+
AND (
|
|
371
|
+
${searchPattern}::text IS NULL
|
|
372
|
+
OR message ILIKE ${searchPattern} ESCAPE '\'
|
|
373
|
+
OR metadata::text ILIKE ${searchPattern} ESCAPE '\'
|
|
374
|
+
)
|
|
375
|
+
GROUP BY level
|
|
376
|
+
ORDER BY count DESC, key ASC
|
|
377
|
+
LIMIT ${limit}
|
|
378
|
+
`;
|
|
379
|
+
return rows;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const rows = await sql<{ key: string; count: number }[]>`
|
|
383
|
+
SELECT source as key, COUNT(*)::int as count
|
|
384
|
+
FROM logging.entries
|
|
385
|
+
WHERE (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
386
|
+
AND (${hasSources}::boolean = false OR source = ANY(${sourceLiteral}::text[]))
|
|
387
|
+
AND (${level}::text IS NULL OR level = ${level})
|
|
388
|
+
AND (
|
|
389
|
+
${searchPattern}::text IS NULL
|
|
390
|
+
OR message ILIKE ${searchPattern} ESCAPE '\'
|
|
391
|
+
OR metadata::text ILIKE ${searchPattern} ESCAPE '\'
|
|
392
|
+
)
|
|
393
|
+
GROUP BY source
|
|
394
|
+
ORDER BY count DESC, key ASC
|
|
395
|
+
LIMIT ${limit}
|
|
396
|
+
`;
|
|
397
|
+
return rows;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
/** Get a single log entry by id. */
|
|
401
|
+
const getById = async (id: string): Promise<LogEntry | null> => {
|
|
402
|
+
const rows = await sql<DbLogRow[]>`
|
|
403
|
+
SELECT id, level, source, message, metadata, created_at
|
|
404
|
+
FROM logging.entries
|
|
405
|
+
WHERE id = ${id}::bigint
|
|
406
|
+
`;
|
|
407
|
+
return rows[0] ? mapRow(rows[0]) : null;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
/** Delete log entries older than the given number of days. */
|
|
411
|
+
const cleanup = async (olderThanDays: number): Promise<{ deleted: number }> => {
|
|
412
|
+
const rows = await sql`
|
|
413
|
+
DELETE FROM logging.entries
|
|
414
|
+
WHERE created_at < now() - ${olderThanDays + " days"}::interval
|
|
415
|
+
`;
|
|
416
|
+
return { deleted: rows.count };
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
export type LogSummary = {
|
|
420
|
+
total: number;
|
|
421
|
+
errors24h: number;
|
|
422
|
+
warnings24h: number;
|
|
423
|
+
total24h: number;
|
|
424
|
+
sources: number;
|
|
425
|
+
lastErrorAt: string | null;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Aggregated stats for the admin dashboard. Single SQL roundtrip, but split
|
|
430
|
+
* into independent subqueries so each one can use `idx_logging_entries_level`
|
|
431
|
+
* and `idx_logging_level_created_at` (composite, see migration). The original
|
|
432
|
+
* `COUNT(*) FILTER (...)` form forced a full-table scan because there was no
|
|
433
|
+
* top-level `WHERE`.
|
|
434
|
+
*
|
|
435
|
+
* `last_error_at::text` because `bun.sql` returns timestamps as `Date` and the
|
|
436
|
+
* `LogSummary.lastErrorAt: string | null` contract expects an ISO-ish string.
|
|
437
|
+
*/
|
|
438
|
+
const summary = async (): Promise<LogSummary> => {
|
|
439
|
+
const [row] = await sql<
|
|
440
|
+
{
|
|
441
|
+
total: number;
|
|
442
|
+
total_24h: number;
|
|
443
|
+
errors_24h: number;
|
|
444
|
+
warnings_24h: number;
|
|
445
|
+
sources: number;
|
|
446
|
+
last_error_at: string | null;
|
|
447
|
+
}[]
|
|
448
|
+
>`
|
|
449
|
+
SELECT
|
|
450
|
+
(SELECT COUNT(*)::int FROM logging.entries) AS total,
|
|
451
|
+
(SELECT COUNT(*)::int FROM logging.entries WHERE created_at > now() - interval '24 hours') AS total_24h,
|
|
452
|
+
(SELECT COUNT(*)::int FROM logging.entries WHERE level = 'error' AND created_at > now() - interval '24 hours') AS errors_24h,
|
|
453
|
+
(SELECT COUNT(*)::int FROM logging.entries WHERE level = 'warn' AND created_at > now() - interval '24 hours') AS warnings_24h,
|
|
454
|
+
(SELECT COUNT(*)::int FROM (SELECT DISTINCT source FROM logging.entries) s) AS sources,
|
|
455
|
+
(SELECT created_at::text FROM logging.entries WHERE level = 'error' ORDER BY created_at DESC LIMIT 1) AS last_error_at
|
|
456
|
+
`;
|
|
457
|
+
return {
|
|
458
|
+
total: row?.total ?? 0,
|
|
459
|
+
errors24h: row?.errors_24h ?? 0,
|
|
460
|
+
warnings24h: row?.warnings_24h ?? 0,
|
|
461
|
+
total24h: row?.total_24h ?? 0,
|
|
462
|
+
sources: row?.sources ?? 0,
|
|
463
|
+
lastErrorAt: row?.last_error_at ?? null,
|
|
464
|
+
};
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
/** Admin service object for querying/managing logs. */
|
|
468
|
+
export const logging = {
|
|
469
|
+
list,
|
|
470
|
+
getById,
|
|
471
|
+
getSources,
|
|
472
|
+
cleanup,
|
|
473
|
+
summary,
|
|
474
|
+
statsBy,
|
|
475
|
+
timeseries,
|
|
476
|
+
trace,
|
|
477
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys whose values are scrubbed from log and trace metadata before storage.
|
|
3
|
+
* Match is case-insensitive and substring-based on the key (e.g. `apiKey`,
|
|
4
|
+
* `accessToken`, `clientSecret` all trip).
|
|
5
|
+
*/
|
|
6
|
+
const SENSITIVE_KEY_PATTERN = /(password|secret|token|cookie|authorization|api[_-]?key|private[_-]?key|session)/i;
|
|
7
|
+
|
|
8
|
+
export const REDACTED = "[REDACTED]";
|
|
9
|
+
|
|
10
|
+
export const isSensitiveMetadataKey = (key: string): boolean => SENSITIVE_KEY_PATTERN.test(key);
|
|
11
|
+
|
|
12
|
+
export const redactMetadata = (input: unknown): unknown => {
|
|
13
|
+
if (input === null || typeof input !== "object") return input;
|
|
14
|
+
if (Array.isArray(input)) return input.map(redactMetadata);
|
|
15
|
+
const out: Record<string, unknown> = {};
|
|
16
|
+
for (const [key, value] of Object.entries(input)) {
|
|
17
|
+
out[key] = isSensitiveMetadataKey(key) ? REDACTED : redactMetadata(value);
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
};
|