@k2b/cloud 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +22 -11
- package/scripts/README.md +10 -0
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +20 -35
- package/scripts/preload.ts +16 -12
- package/scripts/sync-recovery-smoke.ts +22 -6
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/build-metadata.ts +8 -1
- package/src/_internal/capabilities.ts +131 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +80 -37
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/readiness.ts +4 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +18 -151
- package/src/access/PrincipalPicker.tsx +110 -0
- package/src/access/ResourceApiKeys.tsx +1 -1
- package/src/access/ui.ts +1 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +201 -0
- package/src/ai/browser.ts +4 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +229 -90
- package/src/ai/chat/builtin-tools.tsx +2 -4
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +83 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +25 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +31 -47
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +21 -2
- package/src/ai/chat/tool-groups.ts +58 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +14 -10
- package/src/ai/chat-tasks.ts +297 -43
- package/src/ai/client/controller.ts +165 -39
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +29 -11
- package/src/ai/client/transport.ts +6 -0
- package/src/ai/code-capability-routes.ts +105 -0
- package/src/ai/code-capability-transport.ts +39 -0
- package/src/ai/code-execution.ts +53 -0
- package/src/ai/code-mode-skill.ts +118 -0
- package/src/ai/code-runtime-tools.ts +179 -0
- package/src/ai/code-source-contracts.ts +270 -0
- package/src/ai/code-source-tools.ts +188 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +99 -3
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +49 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +189 -33
- package/src/ai/fetch-file-tool.ts +2 -2
- package/src/ai/file-content-version.ts +27 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +8 -1
- package/src/ai/files-store.ts +212 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +42 -10
- package/src/ai/http.ts +26 -8
- package/src/ai/index.ts +26 -3
- package/src/ai/inference-calls.ts +197 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/live.ts +1 -1
- package/src/ai/markdown-pdf-tool.ts +2 -2
- package/src/ai/memories.ts +30 -27
- package/src/ai/memory-learning-runs.ts +1 -1
- package/src/ai/memory-learning.ts +64 -32
- package/src/ai/memory-tool.ts +1 -5
- package/src/ai/memory-workflow-evidence.ts +9 -4
- package/src/ai/message-queue.ts +178 -0
- package/src/ai/migrate.ts +189 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +75 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +18 -43
- package/src/ai/projects.ts +33 -14
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +192 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +241 -11
- package/src/ai/run-timeout.ts +12 -0
- package/src/ai/runtime-tools.ts +7 -5
- package/src/ai/runtime.ts +74 -17
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +4 -5
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +58 -0
- package/src/ai/skill-seeds.ts +114 -20
- package/src/ai/skill-tool.ts +21 -5
- package/src/ai/skills-routes.ts +49 -4
- package/src/ai/skills.ts +358 -163
- package/src/ai/store.ts +416 -201
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +12 -1
- package/src/ai/system-prompt.ts +14 -2
- package/src/ai/task-contracts.ts +84 -0
- package/src/ai/task-execution.ts +92 -0
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +32 -0
- package/src/ai/todo-tool.ts +19 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +3 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +60 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +80 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-linux-identities.ts +10 -4
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/app-approval.ts +2 -2
- package/src/api/auth.ts +1 -1
- package/src/api/capabilities.ts +324 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +8 -2
- package/src/api/mcp.ts +45 -34
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +67 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +6 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +116 -35
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +77 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +10 -1
- package/src/cli/admin/instance.ts +2 -2
- package/src/cli/admin/linux.ts +4 -2
- package/src/cli/admin/sync.ts +1 -1
- package/src/cli/capabilities.ts +42 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/define-env.ts +97 -0
- package/src/config/env.ts +147 -51
- package/src/config/index.ts +3 -1
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +75 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +5 -2
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +5 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +18 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/index.ts +1 -0
- package/src/desktop/solid.tsx +1 -1
- package/src/index.ts +8 -6
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +3 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/notification-sender.ts +12 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/app-approval.ts +11 -10
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +8 -5
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/identity/runtime-config.ts +6 -5
- package/src/services/index.ts +15 -0
- package/src/services/ipa/posix.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +21 -4
- package/src/services/mandates/policy.ts +165 -2
- package/src/services/notifications/batches.ts +125 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +131 -72
- package/src/services/pdf/index.ts +11 -8
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +29 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/app.ts +5 -1
- package/src/services/settings/core-settings.ts +26 -35
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/index.ts +3 -3
- package/src/services/settings/store.ts +58 -84
- package/src/services/webauthn.ts +1 -1
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +3 -0
- package/src/shared/markdown/extensions/info-blocks.ts +1 -1
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +30 -16
- package/src/ssr/AppLaunchpad.island.tsx +33 -136
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +22 -23
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MinimalLayout.tsx +1 -1
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +11 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/index.ts +2 -2
- package/src/workflows/ai/runtime.ts +19 -90
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai/types.ts +6 -82
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +4 -2
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/server/services/freeipa/test-certificates.ts +0 -44
- package/src/services/notifications/live.ts +0 -50
- package/src/services/session/test-fixture.ts +0 -18
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
|
@@ -4,6 +4,7 @@ import { escapeLikePattern, parsePgJsonRecord, toPgTextArray } from "../postgres
|
|
|
4
4
|
import { registerSettings } from "../settings/defaults";
|
|
5
5
|
import { redactMetadata } from "./redaction";
|
|
6
6
|
import { observeSyncEvent, TRACE_STUCK_AFTER_MS, trace } from "./trace";
|
|
7
|
+
import { readWebVitals } from "./web-vitals";
|
|
7
8
|
|
|
8
9
|
export type {
|
|
9
10
|
TraceAttributes,
|
|
@@ -129,7 +130,7 @@ function write(params: WriteParams): void {
|
|
|
129
130
|
${params.level},
|
|
130
131
|
${params.source},
|
|
131
132
|
${params.message},
|
|
132
|
-
${safeMetadata ? JSON.stringify(safeMetadata) : null}::jsonb
|
|
133
|
+
(${safeMetadata ? JSON.stringify(safeMetadata) : null}::text)::jsonb
|
|
133
134
|
)
|
|
134
135
|
`.catch((err: Error) => console.error("[logging] DB write failed:", err.message));
|
|
135
136
|
}
|
|
@@ -194,7 +195,7 @@ const list = async (
|
|
|
194
195
|
WHERE source = ${filterSource}
|
|
195
196
|
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
196
197
|
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 '
|
|
198
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
198
199
|
`;
|
|
199
200
|
|
|
200
201
|
dataRows = await sql`
|
|
@@ -203,7 +204,7 @@ const list = async (
|
|
|
203
204
|
WHERE source = ${filterSource}
|
|
204
205
|
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
205
206
|
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 '
|
|
207
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
207
208
|
ORDER BY created_at DESC
|
|
208
209
|
LIMIT ${perPage} OFFSET ${offset}
|
|
209
210
|
`;
|
|
@@ -214,7 +215,7 @@ const list = async (
|
|
|
214
215
|
WHERE source = ANY(${filterSourcesLiteral}::text[])
|
|
215
216
|
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
216
217
|
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 '
|
|
218
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
218
219
|
`;
|
|
219
220
|
|
|
220
221
|
dataRows = await sql`
|
|
@@ -223,7 +224,7 @@ const list = async (
|
|
|
223
224
|
WHERE source = ANY(${filterSourcesLiteral}::text[])
|
|
224
225
|
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
225
226
|
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 '
|
|
227
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
227
228
|
ORDER BY created_at DESC
|
|
228
229
|
LIMIT ${perPage} OFFSET ${offset}
|
|
229
230
|
`;
|
|
@@ -233,7 +234,7 @@ const list = async (
|
|
|
233
234
|
FROM logging.entries
|
|
234
235
|
WHERE (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
235
236
|
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 '
|
|
237
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
237
238
|
`;
|
|
238
239
|
|
|
239
240
|
dataRows = await sql`
|
|
@@ -241,7 +242,7 @@ const list = async (
|
|
|
241
242
|
FROM logging.entries
|
|
242
243
|
WHERE (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
243
244
|
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 '
|
|
245
|
+
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
245
246
|
ORDER BY created_at DESC
|
|
246
247
|
LIMIT ${perPage} OFFSET ${offset}
|
|
247
248
|
`;
|
|
@@ -321,8 +322,8 @@ const timeseries = async (
|
|
|
321
322
|
AND (${level}::text IS NULL OR entries.level = ${level})
|
|
322
323
|
AND (
|
|
323
324
|
${searchPattern}::text IS NULL
|
|
324
|
-
OR entries.message ILIKE ${searchPattern} ESCAPE '
|
|
325
|
-
OR entries.metadata::text ILIKE ${searchPattern} ESCAPE '
|
|
325
|
+
OR entries.message ILIKE ${searchPattern} ESCAPE '\\'
|
|
326
|
+
OR entries.metadata::text ILIKE ${searchPattern} ESCAPE '\\'
|
|
326
327
|
)
|
|
327
328
|
GROUP BY at, entries.level
|
|
328
329
|
)
|
|
@@ -369,8 +370,8 @@ const statsBy = async (
|
|
|
369
370
|
AND (${level}::text IS NULL OR level = ${level})
|
|
370
371
|
AND (
|
|
371
372
|
${searchPattern}::text IS NULL
|
|
372
|
-
OR message ILIKE ${searchPattern} ESCAPE '
|
|
373
|
-
OR metadata::text ILIKE ${searchPattern} ESCAPE '
|
|
373
|
+
OR message ILIKE ${searchPattern} ESCAPE '\\'
|
|
374
|
+
OR metadata::text ILIKE ${searchPattern} ESCAPE '\\'
|
|
374
375
|
)
|
|
375
376
|
GROUP BY level
|
|
376
377
|
ORDER BY count DESC, key ASC
|
|
@@ -387,8 +388,8 @@ const statsBy = async (
|
|
|
387
388
|
AND (${level}::text IS NULL OR level = ${level})
|
|
388
389
|
AND (
|
|
389
390
|
${searchPattern}::text IS NULL
|
|
390
|
-
OR message ILIKE ${searchPattern} ESCAPE '
|
|
391
|
-
OR metadata::text ILIKE ${searchPattern} ESCAPE '
|
|
391
|
+
OR message ILIKE ${searchPattern} ESCAPE '\\'
|
|
392
|
+
OR metadata::text ILIKE ${searchPattern} ESCAPE '\\'
|
|
392
393
|
)
|
|
393
394
|
GROUP BY source
|
|
394
395
|
ORDER BY count DESC, key ASC
|
|
@@ -466,6 +467,7 @@ const summary = async (): Promise<LogSummary> => {
|
|
|
466
467
|
|
|
467
468
|
/** Admin service object for querying/managing logs. */
|
|
468
469
|
export const logging = {
|
|
470
|
+
webVitals: readWebVitals,
|
|
469
471
|
list,
|
|
470
472
|
getById,
|
|
471
473
|
getSources,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import type { SyncEvent } from "@k2b/sync";
|
|
3
3
|
import { sql } from "bun";
|
|
4
|
+
import { getProcessApplicationId } from "../../_internal/process-identity";
|
|
4
5
|
import type { PaginationParams } from "../../contracts/shared";
|
|
5
6
|
import { escapeLikePattern, parsePgJsonRecord, toPgTextArray } from "../postgres";
|
|
6
7
|
import { isSensitiveMetadataKey, REDACTED, redactMetadata } from "./redaction";
|
|
@@ -1034,7 +1035,7 @@ const detailAttributes = (detail: Record<string, unknown>): TraceAttributes => {
|
|
|
1034
1035
|
};
|
|
1035
1036
|
|
|
1036
1037
|
/** The app hosting this process; Sync events carry no app name. */
|
|
1037
|
-
const processAppId =
|
|
1038
|
+
const processAppId = getProcessApplicationId;
|
|
1038
1039
|
|
|
1039
1040
|
const traceSyncEvent = async (event: SyncEvent, application?: string): Promise<void> => {
|
|
1040
1041
|
const resource = event.resource;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
const ranges = {
|
|
5
|
+
"1h": { hours: 1, bucket: 60 },
|
|
6
|
+
"6h": { hours: 6, bucket: 300 },
|
|
7
|
+
"24h": { hours: 24, bucket: 900 },
|
|
8
|
+
"7d": { hours: 168, bucket: 3600 },
|
|
9
|
+
"30d": { hours: 720, bucket: 21600 },
|
|
10
|
+
} as const;
|
|
11
|
+
const querySchema = z.object({
|
|
12
|
+
range: z.enum(["1h", "6h", "24h", "7d", "30d"]).default("24h"),
|
|
13
|
+
appId: z.string().max(64).default(""),
|
|
14
|
+
route: z.string().max(200).default(""),
|
|
15
|
+
page: z.number().int().min(1).max(100000).default(1),
|
|
16
|
+
});
|
|
17
|
+
export type WebVitalsQuery = z.input<typeof querySchema>;
|
|
18
|
+
export type WebVitalName = "LCP" | "INP" | "CLS";
|
|
19
|
+
export type WebVitalMetric = { name: WebVitalName; count: number; p75: number };
|
|
20
|
+
export type WebVitalsOverview = {
|
|
21
|
+
summary: WebVitalMetric[];
|
|
22
|
+
series: (WebVitalMetric & { bucket: number })[];
|
|
23
|
+
routes: (WebVitalMetric & { appId: string; route: string })[];
|
|
24
|
+
totalRoutes: number;
|
|
25
|
+
page: number;
|
|
26
|
+
perPage: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Admin diagnostics. Deduplicate the last valid report inside [since, until).
|
|
30
|
+
* Attribute it to its last receipt time; this is not navigation-start time.
|
|
31
|
+
* Only aggregates leave Postgres. Retention applies before this read.
|
|
32
|
+
*/
|
|
33
|
+
export async function readWebVitals(input: WebVitalsQuery, until = new Date()): Promise<WebVitalsOverview> {
|
|
34
|
+
const query = querySchema.parse(input);
|
|
35
|
+
const { hours, bucket } = ranges[query.range];
|
|
36
|
+
const since = new Date(until.getTime() - hours * 3600000);
|
|
37
|
+
const perPage = 50;
|
|
38
|
+
const [result] = await sql<Omit<WebVitalsOverview, "page" | "perPage">[]>`
|
|
39
|
+
WITH stored AS MATERIALIZED (
|
|
40
|
+
SELECT id, created_at,
|
|
41
|
+
logging.object_metadata(metadata) AS metadata
|
|
42
|
+
FROM logging.entries
|
|
43
|
+
WHERE source = 'web-vitals' AND level = 'info' AND created_at >= ${since} AND created_at < ${until}
|
|
44
|
+
), candidates AS MATERIALIZED (
|
|
45
|
+
SELECT id, created_at, metadata->>'appId' AS app, metadata->>'routeTemplate' AS route,
|
|
46
|
+
metadata->>'name' AS name, metadata->>'id' AS metric_id,
|
|
47
|
+
CASE WHEN jsonb_typeof(metadata->'value') = 'number' THEN (metadata->>'value')::numeric END AS value
|
|
48
|
+
FROM stored
|
|
49
|
+
WHERE jsonb_typeof(metadata->'appId') = 'string' AND metadata->>'appId' ~ '^[a-z0-9-]{1,64}$'
|
|
50
|
+
AND jsonb_typeof(metadata->'routeTemplate') = 'string'
|
|
51
|
+
AND length(metadata->>'routeTemplate') BETWEEN 1 AND 200 AND metadata->>'routeTemplate' ~ '^/[a-zA-Z0-9_/:.*-]*$'
|
|
52
|
+
AND jsonb_typeof(metadata->'id') = 'string' AND length(metadata->>'id') BETWEEN 1 AND 100
|
|
53
|
+
AND metadata->>'name' IN ('LCP','INP','CLS')
|
|
54
|
+
AND (${query.appId} = '' OR metadata->>'appId' = ${query.appId})
|
|
55
|
+
AND (${query.route} = '' OR metadata->>'routeTemplate' = ${query.route})
|
|
56
|
+
), latest AS MATERIALIZED (
|
|
57
|
+
SELECT DISTINCT ON (app, route, name, metric_id) app, route, name, value::double precision AS value, created_at
|
|
58
|
+
FROM candidates WHERE value >= 0 AND value <= 1.7976931348623157e308::numeric
|
|
59
|
+
ORDER BY app, route, name, metric_id, created_at DESC, id DESC
|
|
60
|
+
), route_stats AS MATERIALIZED (
|
|
61
|
+
SELECT app, route, name, count(*)::int AS count,
|
|
62
|
+
percentile_cont(0.75) WITHIN GROUP (ORDER BY value) AS p75
|
|
63
|
+
FROM latest GROUP BY app, route, name
|
|
64
|
+
), route_keys AS MATERIALIZED (
|
|
65
|
+
SELECT DISTINCT app, route FROM route_stats ORDER BY app, route LIMIT ${perPage} OFFSET ${(query.page - 1) * perPage}
|
|
66
|
+
)
|
|
67
|
+
SELECT
|
|
68
|
+
COALESCE((SELECT jsonb_agg(s) FROM (SELECT name, count(*)::int AS count, percentile_cont(0.75) WITHIN GROUP (ORDER BY value) AS p75 FROM latest GROUP BY name ORDER BY name) s), '[]'::jsonb) AS summary,
|
|
69
|
+
COALESCE((SELECT jsonb_agg(s ORDER BY s.bucket, s.name) FROM (SELECT name,
|
|
70
|
+
floor(extract(epoch FROM created_at) / ${bucket}) * ${bucket} * 1000 AS bucket,
|
|
71
|
+
count(*)::int AS count, percentile_cont(0.75) WITHIN GROUP (ORDER BY value) AS p75
|
|
72
|
+
FROM latest GROUP BY name, bucket) s), '[]'::jsonb) AS series,
|
|
73
|
+
COALESCE((SELECT jsonb_agg(s ORDER BY s."appId", s.route, s.name) FROM (
|
|
74
|
+
SELECT l.app AS "appId", l.route, l.name, l.count, l.p75
|
|
75
|
+
FROM route_stats l JOIN route_keys k USING (app, route)
|
|
76
|
+
) s), '[]'::jsonb) AS routes,
|
|
77
|
+
(SELECT count(*)::int FROM (SELECT DISTINCT app, route FROM route_stats) r) AS "totalRoutes"
|
|
78
|
+
`;
|
|
79
|
+
if (!result) throw new Error("Web Vitals aggregation returned no result");
|
|
80
|
+
return { ...result, page: query.page, perPage };
|
|
81
|
+
}
|
|
@@ -9,6 +9,10 @@ import { parsePgJsonValue } from "../postgres";
|
|
|
9
9
|
import { isMandatePolicyNarrowing, type MandatePolicyV1, MandatePolicyV1Schema, mandatePolicyAllows, parseMandatePolicy } from "./policy";
|
|
10
10
|
|
|
11
11
|
export {
|
|
12
|
+
type CapabilityGrant,
|
|
13
|
+
CapabilityGrantSchema,
|
|
14
|
+
CapabilityGrantsSchema,
|
|
15
|
+
capabilityGrantAllows,
|
|
12
16
|
isMandatePolicyNarrowing,
|
|
13
17
|
MANDATE_POLICY_MAX_IDENTIFIERS,
|
|
14
18
|
MANDATE_POLICY_VERSION,
|
|
@@ -16,6 +20,9 @@ export {
|
|
|
16
20
|
MandatePolicyV1Schema,
|
|
17
21
|
mandatePolicyAllows,
|
|
18
22
|
parseMandatePolicy,
|
|
23
|
+
type TaskGrant,
|
|
24
|
+
TaskGrantSchema,
|
|
25
|
+
TaskGrantsSchema,
|
|
19
26
|
} from "./policy";
|
|
20
27
|
|
|
21
28
|
export type MandateState = "active" | "paused" | "revoked";
|
|
@@ -176,7 +183,7 @@ const mapMandate = (row: MandateRow): Mandate => ({
|
|
|
176
183
|
revokeReason: row.revoke_reason,
|
|
177
184
|
});
|
|
178
185
|
|
|
179
|
-
const selectMandate = sql`
|
|
186
|
+
const selectMandate = () => sql`
|
|
180
187
|
SELECT id, subject_kind, subject_user_id, subject_service_account_id,
|
|
181
188
|
owner_app_id, workload_type, workload_id, policy, state, revision, expires_at,
|
|
182
189
|
confirmed_at, confirmation_deadline,
|
|
@@ -224,7 +231,7 @@ const parsePolicyForWorkload = (
|
|
|
224
231
|
|
|
225
232
|
const load = async (mandateId: string, db: SQL): Promise<Mandate | null> => {
|
|
226
233
|
if (!UUID.safeParse(mandateId).success) return null;
|
|
227
|
-
const [row] = await db<MandateRow[]>`${selectMandate} WHERE id = ${mandateId}::uuid`;
|
|
234
|
+
const [row] = await db<MandateRow[]>`${selectMandate()} WHERE id = ${mandateId}::uuid`;
|
|
228
235
|
return row ? mapMandate(row) : null;
|
|
229
236
|
};
|
|
230
237
|
|
|
@@ -437,7 +444,7 @@ export const listMandates = async (config: {
|
|
|
437
444
|
const where = conditions.slice(1).reduce((combined, condition) => sql`${combined} AND ${condition}`, conditions[0]!);
|
|
438
445
|
const [countRows, rows] = await Promise.all([
|
|
439
446
|
db<{ count: number }[]>`SELECT COUNT(*)::int AS count FROM auth.mandates WHERE ${where}`,
|
|
440
|
-
db<MandateRow[]>`${selectMandate} WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT ${perPage} OFFSET ${offset}`,
|
|
447
|
+
db<MandateRow[]>`${selectMandate()} WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT ${perPage} OFFSET ${offset}`,
|
|
441
448
|
]);
|
|
442
449
|
const total = countRows[0]?.count ?? 0;
|
|
443
450
|
return { items: rows.map(mapMandate), page, perPage, total, hasNext: page * perPage < total };
|
|
@@ -616,6 +623,8 @@ export const validateMandateIssueAuthority = async (
|
|
|
616
623
|
targetAppId: string;
|
|
617
624
|
operation: string;
|
|
618
625
|
actionApproval?: "none" | "approved";
|
|
626
|
+
input?: unknown;
|
|
627
|
+
capabilityApproval?: "none" | "rememberable" | "always";
|
|
619
628
|
expectedRevision?: number;
|
|
620
629
|
requestId?: string;
|
|
621
630
|
},
|
|
@@ -669,7 +678,15 @@ export const validateMandateIssueAuthority = async (
|
|
|
669
678
|
} catch {
|
|
670
679
|
return denyMandateIssue(err.forbidden("Mandate policy is invalid"));
|
|
671
680
|
}
|
|
672
|
-
if (
|
|
681
|
+
if (
|
|
682
|
+
!mandatePolicyAllows(policy, {
|
|
683
|
+
appId: target.data,
|
|
684
|
+
operation: operation.data,
|
|
685
|
+
actionApproval: input.actionApproval ?? "none",
|
|
686
|
+
input: input.input,
|
|
687
|
+
capabilityApproval: input.capabilityApproval,
|
|
688
|
+
})
|
|
689
|
+
) {
|
|
673
690
|
return denyMandateIssue(err.forbidden("Mandate policy does not allow this operation"));
|
|
674
691
|
}
|
|
675
692
|
const mandate = mapMandate(row);
|
|
@@ -12,6 +12,8 @@ const identifierList = <T extends z.ZodType<string>>(schema: T) =>
|
|
|
12
12
|
|
|
13
13
|
const operationSchema = z.union([
|
|
14
14
|
z.literal("search.query"),
|
|
15
|
+
z.literal("runtime.http"),
|
|
16
|
+
z.literal("runtime.database"),
|
|
15
17
|
z.string().refine((value) => {
|
|
16
18
|
const [prefix, localId, extra] = value.split(":");
|
|
17
19
|
return (
|
|
@@ -25,12 +27,121 @@ const operationSchema = z.union([
|
|
|
25
27
|
}, "Expected a canonical mandate operation"),
|
|
26
28
|
]);
|
|
27
29
|
|
|
30
|
+
export const CapabilityGrantSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
appId: CapabilityAppIdSchema.describe("Exact discovered application ID."),
|
|
33
|
+
capabilityId: CapabilityLocalIdSchema.describe("Exact local capability ID from discovery, without application prefix."),
|
|
34
|
+
kind: z.enum(["query", "action"]).describe("Discovered capability kind."),
|
|
35
|
+
fixedInput: z.record(z.string(), z.json()).default({}).describe("Exact top-level input values. Omitted fields remain unrestricted."),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
export const CapabilityGrantsSchema = z.array(CapabilityGrantSchema).max(MANDATE_POLICY_MAX_IDENTIFIERS / 2);
|
|
39
|
+
export type CapabilityGrant = z.output<typeof CapabilityGrantSchema>;
|
|
40
|
+
|
|
41
|
+
const httpsUrl = z
|
|
42
|
+
.string()
|
|
43
|
+
.url()
|
|
44
|
+
.max(2000)
|
|
45
|
+
.refine((value) => {
|
|
46
|
+
const url = new URL(value);
|
|
47
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
48
|
+
}, "Use HTTPS without credentials or fragment");
|
|
49
|
+
export const TaskGrantSchema = z.union([
|
|
50
|
+
CapabilityGrantSchema,
|
|
51
|
+
z
|
|
52
|
+
.object({
|
|
53
|
+
kind: z.literal("http").describe("Authorize scheduled Code Mode HTTP requests."),
|
|
54
|
+
fixedInput: z
|
|
55
|
+
.object({
|
|
56
|
+
origin: httpsUrl
|
|
57
|
+
.refine((value) => new URL(value).origin === value, "Use an exact origin without path")
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Exact HTTPS origin, without path, wildcard or trailing slash."),
|
|
60
|
+
url: httpsUrl.optional().describe("Exact full URL, including query parameters."),
|
|
61
|
+
method: z
|
|
62
|
+
.enum(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Exact HTTP method; omit to permit every supported method."),
|
|
65
|
+
})
|
|
66
|
+
.strict()
|
|
67
|
+
.default({})
|
|
68
|
+
.describe("Fixed input values. Omitted fields are unrestricted; empty input permits every supported target and operation."),
|
|
69
|
+
})
|
|
70
|
+
.strict(),
|
|
71
|
+
z
|
|
72
|
+
.object({
|
|
73
|
+
kind: z.literal("database").describe("Authorize scheduled Code Mode RSQL operations."),
|
|
74
|
+
fixedInput: z
|
|
75
|
+
.object({
|
|
76
|
+
resourceId: z.string().min(1).max(80).optional().describe("Exact Studio app ID whose database may be used."),
|
|
77
|
+
operation: z
|
|
78
|
+
.enum([
|
|
79
|
+
"connect",
|
|
80
|
+
"status",
|
|
81
|
+
"export",
|
|
82
|
+
"clear",
|
|
83
|
+
"reset",
|
|
84
|
+
"tables.list",
|
|
85
|
+
"tables.create",
|
|
86
|
+
"tables.update",
|
|
87
|
+
"tables.delete",
|
|
88
|
+
"schema.get",
|
|
89
|
+
"rows.list",
|
|
90
|
+
"rows.get",
|
|
91
|
+
"rows.insert",
|
|
92
|
+
"rows.update",
|
|
93
|
+
"rows.delete",
|
|
94
|
+
"query",
|
|
95
|
+
])
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Exact database operation; connect needs its own grant when operation is fixed."),
|
|
98
|
+
table: z
|
|
99
|
+
.string()
|
|
100
|
+
.min(1)
|
|
101
|
+
.max(63)
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("Exact table; applies to row and schema operations, not arbitrary SQL queries."),
|
|
104
|
+
})
|
|
105
|
+
.strict()
|
|
106
|
+
.default({})
|
|
107
|
+
.describe("Fixed input values. Omitted fields are unrestricted; empty input permits every supported target and operation."),
|
|
108
|
+
})
|
|
109
|
+
.strict(),
|
|
110
|
+
]);
|
|
111
|
+
export const TaskGrantsSchema = z.array(TaskGrantSchema).max(MANDATE_POLICY_MAX_IDENTIFIERS / 2);
|
|
112
|
+
export type TaskGrant = z.output<typeof TaskGrantSchema>;
|
|
113
|
+
|
|
114
|
+
const equalJson = (a: unknown, b: unknown): boolean => {
|
|
115
|
+
if (a === b) return true;
|
|
116
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
|
|
117
|
+
if (Array.isArray(a) || Array.isArray(b))
|
|
118
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => equalJson(v, b[i]));
|
|
119
|
+
const ak = Object.keys(a),
|
|
120
|
+
bk = Object.keys(b);
|
|
121
|
+
return ak.length === bk.length && ak.every((key) => Object.hasOwn(b, key) && equalJson(Reflect.get(a, key), Reflect.get(b, key)));
|
|
122
|
+
};
|
|
123
|
+
export const capabilityGrantAllows = (
|
|
124
|
+
grant: CapabilityGrant,
|
|
125
|
+
input: { appId: string; capabilityId: string; kind: "query" | "action"; input: unknown },
|
|
126
|
+
): boolean =>
|
|
127
|
+
grant.appId === input.appId &&
|
|
128
|
+
grant.capabilityId === input.capabilityId &&
|
|
129
|
+
grant.kind === input.kind &&
|
|
130
|
+
Object.entries(grant.fixedInput).every(
|
|
131
|
+
([key, value]) =>
|
|
132
|
+
input.input !== null &&
|
|
133
|
+
typeof input.input === "object" &&
|
|
134
|
+
Object.hasOwn(input.input, key) &&
|
|
135
|
+
equalJson(value, Reflect.get(input.input, key)),
|
|
136
|
+
);
|
|
137
|
+
|
|
28
138
|
export const MandatePolicyV1Schema = z
|
|
29
139
|
.object({
|
|
30
140
|
version: z.literal(MANDATE_POLICY_VERSION),
|
|
31
141
|
apps: z.union([z.literal("*"), identifierList(CapabilityAppIdSchema)]),
|
|
32
142
|
operations: z.union([z.literal("*"), identifierList(operationSchema)]),
|
|
33
143
|
actions: z.enum(["deny", "require_approval", "preapproved"]),
|
|
144
|
+
grants: TaskGrantsSchema.optional(),
|
|
34
145
|
})
|
|
35
146
|
.strict()
|
|
36
147
|
.superRefine((policy, context) => {
|
|
@@ -70,14 +181,66 @@ const ACTION_AUTHORITY = { deny: 0, require_approval: 1, preapproved: 2 } as con
|
|
|
70
181
|
export const isMandatePolicyNarrowing = (current: MandatePolicyV1, next: MandatePolicyV1): boolean =>
|
|
71
182
|
setAllows(current.apps, next.apps) &&
|
|
72
183
|
setAllows(current.operations, next.operations) &&
|
|
73
|
-
ACTION_AUTHORITY[next.actions] <= ACTION_AUTHORITY[current.actions]
|
|
184
|
+
ACTION_AUTHORITY[next.actions] <= ACTION_AUTHORITY[current.actions] &&
|
|
185
|
+
(current.grants === undefined
|
|
186
|
+
? !next.grants?.some((grant) => grant.kind === "http" || grant.kind === "database")
|
|
187
|
+
: next.grants !== undefined &&
|
|
188
|
+
next.grants.every((grant) =>
|
|
189
|
+
current.grants!.some((existing) => {
|
|
190
|
+
if (existing.kind !== grant.kind) return false;
|
|
191
|
+
if ("appId" in existing && "appId" in grant && (existing.appId !== grant.appId || existing.capabilityId !== grant.capabilityId))
|
|
192
|
+
return false;
|
|
193
|
+
return Object.entries(existing.fixedInput).every(([key, value]) => equalJson(value, Reflect.get(grant.fixedInput, key)));
|
|
194
|
+
}),
|
|
195
|
+
));
|
|
74
196
|
|
|
75
197
|
export const mandatePolicyAllows = (
|
|
76
198
|
policy: MandatePolicyV1,
|
|
77
|
-
input: {
|
|
199
|
+
input: {
|
|
200
|
+
appId: string;
|
|
201
|
+
operation: string;
|
|
202
|
+
actionApproval: "none" | "approved";
|
|
203
|
+
input?: unknown;
|
|
204
|
+
capabilityApproval?: "none" | "rememberable" | "always";
|
|
205
|
+
},
|
|
78
206
|
): boolean => {
|
|
79
207
|
if (policy.apps !== "*" && !policy.apps.includes(input.appId)) return false;
|
|
80
208
|
if (policy.operations !== "*" && !policy.operations.includes(input.operation)) return false;
|
|
209
|
+
if (input.operation === "runtime.http" || input.operation === "runtime.database") {
|
|
210
|
+
const kind = input.operation === "runtime.http" ? "http" : "database";
|
|
211
|
+
return (
|
|
212
|
+
input.appId === "assistant" &&
|
|
213
|
+
!!policy.grants?.some(
|
|
214
|
+
(grant) =>
|
|
215
|
+
grant.kind === kind &&
|
|
216
|
+
Object.entries(grant.fixedInput).every(
|
|
217
|
+
([key, value]) =>
|
|
218
|
+
input.input !== null &&
|
|
219
|
+
typeof input.input === "object" &&
|
|
220
|
+
Object.hasOwn(input.input, key) &&
|
|
221
|
+
equalJson(value, Reflect.get(input.input, key)),
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
if (policy.grants !== undefined) {
|
|
227
|
+
const [operation, capabilityId] = input.operation.split(":");
|
|
228
|
+
const kind =
|
|
229
|
+
operation === "capability.query"
|
|
230
|
+
? "query"
|
|
231
|
+
: operation === "capability.action.run" || operation === "capability.action.review"
|
|
232
|
+
? "action"
|
|
233
|
+
: null;
|
|
234
|
+
if (
|
|
235
|
+
!kind ||
|
|
236
|
+
!capabilityId ||
|
|
237
|
+
!policy.grants.some(
|
|
238
|
+
(grant) => "appId" in grant && capabilityGrantAllows(grant, { appId: input.appId, capabilityId, kind, input: input.input }),
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
return false;
|
|
242
|
+
if (kind === "action" && input.capabilityApproval !== "none" && input.capabilityApproval !== "rememberable") return false;
|
|
243
|
+
}
|
|
81
244
|
if (!input.operation.startsWith("capability.action.run:")) return true;
|
|
82
245
|
if (policy.actions === "deny") return false;
|
|
83
246
|
if (policy.actions === "require_approval") return input.actionApproval === "approved";
|