@k2b/cloud 0.43.0 → 0.45.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/package.json +11 -2
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +158 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +7 -5
- package/src/_internal/define-app.ts +57 -6
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +11 -1
- package/src/_internal/registry.ts +8 -1
- package/src/_internal/runtime-context.ts +8 -1
- package/src/access/AppAccessEditor.tsx +2 -0
- package/src/access/GroupCoverage.tsx +20 -5
- package/src/access/PermissionEditor.tsx +20 -5
- package/src/access/PrincipalAvatar.tsx +30 -21
- package/src/access/PrincipalPicker.tsx +14 -4
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-messages.ts +6 -0
- package/src/access/messages.ts +4 -4
- package/src/account/EntitySearch.tsx +12 -0
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +7 -0
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +10 -6
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +40 -25
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +12 -5
- package/src/ai/inference-calls.ts +5 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api.ts +26 -9
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/project-tool.ts +4 -4
- package/src/ai/projects-authorization.ts +53 -0
- package/src/ai/projects-routes.ts +58 -47
- package/src/ai/projects.ts +110 -88
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +55 -6
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider.ts +1 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +4 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +9 -3
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +47 -48
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/skill-tool.ts +4 -4
- package/src/ai/skills-routes.ts +27 -26
- package/src/ai/skills.ts +120 -81
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +21 -44
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +27 -22
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +22 -3
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-skills.ts +4 -1
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +35 -5
- package/src/api/capabilities.ts +28 -4
- package/src/api/index.ts +7 -1
- package/src/api/mcp.ts +175 -2
- package/src/api/me.ts +6 -4
- package/src/api/pwa-phone.ts +75 -26
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/app-pwa/contracts.ts +4 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +1 -0
- package/src/app-pwa/service.ts +91 -0
- package/src/app-pwa/transport.ts +22 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/session.ts +39 -0
- package/src/browser/shared-text-history-validation.ts +151 -0
- package/src/browser/shared-text-history.ts +464 -0
- package/src/browser/shared-text-offline-authorized.ts +29 -0
- package/src/browser/shared-text-offline.ts +351 -0
- package/src/browser/shared-text-provider.ts +714 -0
- package/src/browser/shared-text-table-contract.ts +150 -0
- package/src/browser/shared-text.ts +19 -0
- package/src/capabilities/server-invoke.ts +2 -11
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +5 -2
- package/src/cli/apps.ts +89 -26
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +26 -2
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +21 -5
- package/src/contracts/capabilities.ts +143 -3
- package/src/contracts/index.ts +2 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/pwa.ts +17 -1
- package/src/contracts/registry.ts +6 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +6 -1
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +1 -1
- package/src/server/api/respond.ts +17 -4
- package/src/server/index.ts +2 -0
- package/src/server/middleware/app-access.ts +1 -1
- package/src/server/middleware/auth.ts +21 -7
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +2 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +31 -10
- package/src/server/services/index.ts +1 -0
- package/src/services/accounts/app.ts +36 -13
- package/src/services/accounts/entities.ts +43 -19
- package/src/services/accounts/group-sql.ts +23 -0
- package/src/services/accounts/groups.ts +19 -14
- package/src/services/accounts/identities.ts +70 -11
- package/src/services/accounts/identity-reconciliation.ts +3 -1
- package/src/services/accounts/local-groups.ts +69 -4
- package/src/services/accounts/users.ts +45 -14
- package/src/services/app-access.ts +25 -7
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/ledger.ts +28 -10
- package/src/services/geo/service.ts +43 -24
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/invocation-authority.ts +4 -0
- package/src/services/identity/invocation-token.ts +238 -56
- package/src/services/identity/issue-invocation.ts +6 -1
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +13 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/index.ts +2 -2
- package/src/services/mandates/policy.ts +20 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/session/index.ts +26 -3
- package/src/services/settings/core-settings.ts +71 -43
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/email-html.ts +1 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/markdown/extensions/katex.ts +8 -1
- package/src/shared/markdown/extensions/tables.ts +14 -1
- package/src/shared/markdown/index.ts +22 -11
- package/src/shared-text/anchors.ts +29 -0
- package/src/shared-text/awareness.ts +87 -0
- package/src/shared-text/changes.ts +27 -0
- package/src/shared-text/documents.ts +433 -65
- package/src/shared-text/events.ts +12 -2
- package/src/shared-text/index.ts +16 -1
- package/src/shared-text/log.ts +24 -5
- package/src/shared-text/snapshots.ts +69 -23
- package/src/shared-text/structural-history.ts +377 -0
- package/src/shared-text/table-actions.ts +432 -0
- package/src/shared-text/text-diff.ts +75 -0
- package/src/shared-text/types.ts +24 -5
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +9 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +2 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +44 -7
- package/src/ssr/Layout.tsx +9 -7
- package/src/ssr/MobileProfileActions.tsx +2 -2
- package/src/ssr/ProfilePreferences.tsx +2 -1
- package/src/ssr/PwaLayout.tsx +16 -1
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -31
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-launchpad.tsx +6 -1
- package/src/ssr/index.ts +8 -0
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +11 -1
- package/src/ssr/profile-actions.ts +3 -6
- package/src/ssr/pwa-messages.ts +2 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +3 -1
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/language/expressions.ts +3 -2
- package/src/workflows/runtime/executor.ts +134 -34
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- package/src/shared/ai-model-request-settings.ts +0 -21
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PlatformPermissionSchema } from "./outgoing-mail";
|
|
3
|
+
import { RoleSchema } from "./shared";
|
|
4
|
+
|
|
5
|
+
/** Match the administrative event API's maximum page size. Totals make truncation visible. */
|
|
6
|
+
export const APP_DETAILS_LIST_LIMIT = 100;
|
|
7
|
+
export const APP_DETAILS_FAILED_LIMIT = 5;
|
|
8
|
+
export const APP_SUBSCRIPTION_NEW_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
const count = z.number().int().nonnegative();
|
|
11
|
+
const deliveryCounts = z.object({ pending: count, retrying: count, done: count, failed: count, cancelled: count });
|
|
12
|
+
|
|
13
|
+
export const AppDetailsSchema = z.object({
|
|
14
|
+
app: z.object({
|
|
15
|
+
id: z.string(),
|
|
16
|
+
name: z.string(),
|
|
17
|
+
description: z.string().nullable(),
|
|
18
|
+
icon: z.string().nullable(),
|
|
19
|
+
accent: z.templateLiteral(["#", z.string()]).nullable(),
|
|
20
|
+
online: z.boolean(),
|
|
21
|
+
}),
|
|
22
|
+
access: z.object({
|
|
23
|
+
/** Null means Core has no current access declaration for an offline app. */
|
|
24
|
+
kind: z.enum(["grants", "signedIn", "noGuests", "roles", "resources", "legacy"]).nullable(),
|
|
25
|
+
roles: z.array(RoleSchema),
|
|
26
|
+
grantCount: count.nullable(),
|
|
27
|
+
platformPermissions: z.array(PlatformPermissionSchema),
|
|
28
|
+
}),
|
|
29
|
+
sends: z
|
|
30
|
+
.array(
|
|
31
|
+
z.object({
|
|
32
|
+
type: z.string(),
|
|
33
|
+
version: z.number().int().positive(),
|
|
34
|
+
title: z.string(),
|
|
35
|
+
description: z.string(),
|
|
36
|
+
subject: z.string(),
|
|
37
|
+
place: z.string().nullable(),
|
|
38
|
+
automation: z.boolean(),
|
|
39
|
+
subscriberCount: count,
|
|
40
|
+
}),
|
|
41
|
+
)
|
|
42
|
+
.max(APP_DETAILS_LIST_LIMIT),
|
|
43
|
+
hears: z
|
|
44
|
+
.array(
|
|
45
|
+
z.object({
|
|
46
|
+
subscriptionId: z.string(),
|
|
47
|
+
eventType: z.string(),
|
|
48
|
+
version: z.number().int().positive(),
|
|
49
|
+
publisherAppId: z.string(),
|
|
50
|
+
title: z.string().nullable(),
|
|
51
|
+
state: z.enum(["active", "blocked", "cancelled"]),
|
|
52
|
+
reason: z.string().nullable(),
|
|
53
|
+
firstSeenAt: z.iso.datetime(),
|
|
54
|
+
isNew: z.boolean(),
|
|
55
|
+
counts: deliveryCounts,
|
|
56
|
+
failedDeliveries: z
|
|
57
|
+
.array(
|
|
58
|
+
z.object({
|
|
59
|
+
id: z.uuid(),
|
|
60
|
+
eventId: z.uuid(),
|
|
61
|
+
code: z.string().nullable(),
|
|
62
|
+
lastError: z.string().max(1000).nullable(),
|
|
63
|
+
attempts: count,
|
|
64
|
+
lastAttemptAt: z.iso.datetime().nullable(),
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
67
|
+
.max(APP_DETAILS_FAILED_LIMIT),
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
.max(APP_DETAILS_LIST_LIMIT),
|
|
71
|
+
notifications: z
|
|
72
|
+
.array(
|
|
73
|
+
z.object({
|
|
74
|
+
id: z.string(),
|
|
75
|
+
kind: z.string(),
|
|
76
|
+
label: z.string(),
|
|
77
|
+
description: z.string(),
|
|
78
|
+
recipientKind: z.enum(["user", "email"]),
|
|
79
|
+
requiredChannels: z.array(z.string()),
|
|
80
|
+
recommendedChannels: z.array(z.string()),
|
|
81
|
+
excludedChannels: z.array(z.string()),
|
|
82
|
+
excludedReason: z.string().nullable(),
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
.max(APP_DETAILS_LIST_LIMIT),
|
|
86
|
+
totals: z.object({ sends: count, hears: count, notifications: count }),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export type AppDetails = z.infer<typeof AppDetailsSchema>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CapabilityAppIdSchema, CapabilityQualifiedIdSchema, CloudResourceRefSchema } from "./capabilities";
|
|
3
|
+
|
|
4
|
+
/** Maximum PostgreSQL jsonb-text size of the complete envelope, including separator spaces. */
|
|
5
|
+
export const APP_EVENT_MAX_BYTES = 4096;
|
|
6
|
+
|
|
7
|
+
export const AppEventEnvelopeSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
id: z.uuid(),
|
|
10
|
+
type: CapabilityQualifiedIdSchema,
|
|
11
|
+
version: z.number().int().positive(),
|
|
12
|
+
source: CapabilityAppIdSchema,
|
|
13
|
+
time: z.iso.datetime({ offset: true }),
|
|
14
|
+
subject: CloudResourceRefSchema,
|
|
15
|
+
place: CloudResourceRefSchema.optional(),
|
|
16
|
+
cause: z
|
|
17
|
+
.object({ requestId: z.string().min(1).max(256) })
|
|
18
|
+
.strict()
|
|
19
|
+
.optional(),
|
|
20
|
+
data: z.json(),
|
|
21
|
+
})
|
|
22
|
+
.strict();
|
|
23
|
+
|
|
24
|
+
export type AppEventEnvelope<Data = unknown> = Omit<z.infer<typeof AppEventEnvelopeSchema>, "data"> & { data: Data };
|
|
25
|
+
|
|
26
|
+
/** Delivery runs as the subscribing service, without a user's request authority. */
|
|
27
|
+
export type AppEventDeliveryContext = {
|
|
28
|
+
subscriptionId: string;
|
|
29
|
+
eventId: string;
|
|
30
|
+
attempt: number;
|
|
31
|
+
signal: AbortSignal;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type AppEventDefinition<Schema extends z.ZodType = z.ZodType> = {
|
|
35
|
+
version: number;
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
subject: string;
|
|
39
|
+
place?: string;
|
|
40
|
+
schema: Schema;
|
|
41
|
+
automation?: false;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type AppEventSubscriptionDefinition<Schema extends z.ZodType = z.ZodType> = {
|
|
45
|
+
version: number;
|
|
46
|
+
schema: Schema;
|
|
47
|
+
run(envelope: AppEventEnvelope<z.output<Schema>>, context: AppEventDeliveryContext): unknown | Promise<unknown>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const appEventSubscriptionId = (subscriber: string, type: string, version: number): string =>
|
|
51
|
+
`${CapabilityAppIdSchema.parse(subscriber)}:${CapabilityQualifiedIdSchema.parse(type)}@${z.number().int().positive().parse(version)}`;
|
package/src/contracts/app.ts
CHANGED
|
@@ -50,9 +50,16 @@ export type AppAdminNavigationLink = {
|
|
|
50
50
|
icon: string;
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
/** Platform admin areas an app can contribute to with `adminNav[].section`. */
|
|
54
|
+
export const APP_ADMIN_AREAS = ["ai", "observability"] as const;
|
|
55
|
+
export type AppAdminArea = (typeof APP_ADMIN_AREAS)[number];
|
|
56
|
+
|
|
53
57
|
export type AppAdminNavigationGroup = {
|
|
54
|
-
/**
|
|
55
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Contribute links to a platform admin area instead of a separate group. The admin sidebar shows the area as one
|
|
60
|
+
* entry; the owner renders the area's columns itself. `"ai"` is the AI area, `"observability"` the Observability area.
|
|
61
|
+
*/
|
|
62
|
+
section?: AppAdminArea;
|
|
56
63
|
/** Stable key used only to localize this presentation group. */
|
|
57
64
|
id?: string;
|
|
58
65
|
label: string;
|
|
@@ -109,6 +116,10 @@ export type AppPwaPart = {
|
|
|
109
116
|
href: string;
|
|
110
117
|
/** Coarse visibility in the mobile app, like `nav.requiresRoles`. Routes and services still authorize. */
|
|
111
118
|
requiresRoles?: Role[];
|
|
119
|
+
/** Same-origin path of a `GET` route returning an `AppNavBadge`, counted beside the part in the app's sidebar. */
|
|
120
|
+
badge?: string;
|
|
121
|
+
/** Same-origin path of a `GET` route returning a `PwaPartSummary`, the line under the part on the app's Start. */
|
|
122
|
+
summary?: string;
|
|
112
123
|
};
|
|
113
124
|
|
|
114
125
|
/** One same-origin panel page contributed by an application. Routes and services still authorize. */
|
|
@@ -127,6 +138,8 @@ export type AppMeta = {
|
|
|
127
138
|
access?: AppAccessRule;
|
|
128
139
|
platformPermissions?: readonly PlatformPermission[];
|
|
129
140
|
id: string;
|
|
141
|
+
/** Shared object-store package; omitted means the app ID. */
|
|
142
|
+
package?: string;
|
|
130
143
|
name: string;
|
|
131
144
|
icon: string;
|
|
132
145
|
description: string;
|
|
@@ -180,12 +193,15 @@ export type AppMeta = {
|
|
|
180
193
|
|
|
181
194
|
/**
|
|
182
195
|
* Response of an app's `nav.badge` route: how many items wait for the signed-in
|
|
183
|
-
* person. A positive safe integer shows a count on the app's icon in the rail
|
|
184
|
-
* and the app grid (above 99 as "99+"); `0`, a non-200
|
|
185
|
-
* else shows none.
|
|
196
|
+
* person. A positive safe integer shows a count on the app's icon in the rail,
|
|
197
|
+
* the phone bar, and the app grid (above 99 as "99+"); `0`, a non-200
|
|
198
|
+
* response, or anything else shows none.
|
|
186
199
|
*/
|
|
187
200
|
export type AppNavBadge = { count: number };
|
|
188
201
|
|
|
202
|
+
/** What a part says about itself on the Cloud app's Start: a line in the reader's locale and the count behind it. */
|
|
203
|
+
export type PwaPartSummary = AppNavBadge & { text: string };
|
|
204
|
+
|
|
189
205
|
/** A widget endpoint and its board presentation; the handler owns resource authorization. */
|
|
190
206
|
export type WidgetEndpoint = {
|
|
191
207
|
/** Unique-within-the-app id, e.g. "open-requests". */
|
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
CapabilityStreamPolicySchema,
|
|
5
5
|
CapabilityStreamSchema,
|
|
6
6
|
} from "./capability-streams";
|
|
7
|
+
import { RESOURCE_PAGE_MAX_LENGTH, RESOURCE_PAGES_PER_TYPE, resourcePageIssue } from "./resource-pages";
|
|
7
8
|
|
|
8
9
|
export * from "./capability-streams";
|
|
9
10
|
|
|
10
11
|
import { z } from "zod";
|
|
11
12
|
import type { RequestAuthority } from "../server/middleware/auth";
|
|
13
|
+
import type { AppEventDefinition, AppEventSubscriptionDefinition } from "./app-events";
|
|
12
14
|
import { CommandPathSchema } from "./commands";
|
|
13
15
|
import type { AccessSubject, RequestActor, User } from "./shared";
|
|
14
16
|
|
|
@@ -56,6 +58,10 @@ export const CAPABILITY_ORIGINS = ["assistant", "mcp", "http", "app"] as const;
|
|
|
56
58
|
export type CapabilityOrigin = (typeof CAPABILITY_ORIGINS)[number];
|
|
57
59
|
export const CapabilityOriginSchema = z.enum(CAPABILITY_ORIGINS);
|
|
58
60
|
|
|
61
|
+
/** Visible content provenance. It is metadata, never an authorization input. */
|
|
62
|
+
export const CapabilityViaSchema = z.enum(["assistant", "mcp"]).nullable();
|
|
63
|
+
export type CapabilityVia = z.infer<typeof CapabilityViaSchema>;
|
|
64
|
+
|
|
59
65
|
/** Transports the resolved origin from the Cloud dispatcher to the app endpoint. */
|
|
60
66
|
export const CAPABILITY_ORIGIN_HEADER = "x-cloud-capability-origin";
|
|
61
67
|
|
|
@@ -97,6 +103,20 @@ export const CloudResourceReferenceSchema = CloudResourceRefSchema.extend({
|
|
|
97
103
|
title: z.string().min(1).max(500).optional().describe("Optional current user-facing resource label."),
|
|
98
104
|
preview: z.string().max(2000).optional().describe("Optional short plain-text context for the resource."),
|
|
99
105
|
icon: z.string().min(1).max(120).optional().describe("Optional presentation icon for the resource."),
|
|
106
|
+
context: z
|
|
107
|
+
.string()
|
|
108
|
+
.min(1)
|
|
109
|
+
.max(200)
|
|
110
|
+
.optional()
|
|
111
|
+
.describe(
|
|
112
|
+
"One line in the caller's locale that says what and where the resource is and its most useful fact, for example 'Task in IT planning · due 30 Oct'.",
|
|
113
|
+
),
|
|
114
|
+
status: z
|
|
115
|
+
.string()
|
|
116
|
+
.min(1)
|
|
117
|
+
.max(60)
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("The resource's current state as a short label in the caller's locale, for example 'Open' or 'Done'."),
|
|
100
120
|
}).strict();
|
|
101
121
|
|
|
102
122
|
export type CloudResourceReference = z.infer<typeof CloudResourceReferenceSchema>;
|
|
@@ -660,7 +680,7 @@ const resultSchema = <T extends z.ZodType>(
|
|
|
660
680
|
stream: CapabilityStreamSchema.optional(),
|
|
661
681
|
summary: z.string().trim().min(1).max(500).optional(),
|
|
662
682
|
refs: z
|
|
663
|
-
.array(options.consumer ? LinkedCloudResourceReferenceSchema : CloudResourceReferenceSchema)
|
|
683
|
+
.array(options.consumer ? LinkedCloudResourceReferenceSchema.strip() : CloudResourceReferenceSchema)
|
|
664
684
|
.max(100)
|
|
665
685
|
.optional(),
|
|
666
686
|
page: CapabilityPageSchema.optional(),
|
|
@@ -727,6 +747,8 @@ export const CloudResourceViewSchema = z
|
|
|
727
747
|
title: z.string().min(1).max(500).describe("Primary result label."),
|
|
728
748
|
preview: z.string().max(2000).optional().describe("Short plain-text result preview."),
|
|
729
749
|
icon: z.string().min(1).max(120).optional().describe("Optional presentation icon."),
|
|
750
|
+
context: CloudResourceReferenceSchema.shape.context,
|
|
751
|
+
status: CloudResourceReferenceSchema.shape.status,
|
|
730
752
|
priority: z.number().int().min(0).max(9).optional().describe("App-local relevance hint from zero to nine."),
|
|
731
753
|
metadata: z
|
|
732
754
|
.array(
|
|
@@ -793,15 +815,25 @@ export type CapabilityExecutionContext = {
|
|
|
793
815
|
requestId: string;
|
|
794
816
|
/** Cloud surface that invoked this capability. */
|
|
795
817
|
origin: CapabilityOrigin;
|
|
818
|
+
/** Framework-derived visible provenance; absent only in manually constructed contexts. */
|
|
819
|
+
via?: CapabilityVia;
|
|
796
820
|
/** Caller cancellation or shared dispatch deadline; forward it to expensive work. */
|
|
797
821
|
signal: AbortSignal;
|
|
798
822
|
};
|
|
799
823
|
|
|
824
|
+
/** Colours a consumer may give a resource type's icon, the same palette as `@k2b/ui` cards. */
|
|
825
|
+
export const CAPABILITY_RESOURCE_COLORS = ["blue", "emerald", "violet", "orange", "red", "amber", "zinc", "cyan", "rose"] as const;
|
|
826
|
+
export type CapabilityResourceColor = (typeof CAPABILITY_RESOURCE_COLORS)[number];
|
|
827
|
+
|
|
800
828
|
export type CapabilityResourceTypeDefinition = {
|
|
801
829
|
title: string;
|
|
802
830
|
description: string;
|
|
803
831
|
icon?: string;
|
|
832
|
+
/** One colour per kind of resource, so a card or list shows at a glance what it stands for. */
|
|
833
|
+
color?: CapabilityResourceColor;
|
|
804
834
|
reader?: string;
|
|
835
|
+
/** Root-relative pages recognized when a person pastes a Cloud link. Requires a reader. */
|
|
836
|
+
pages?: readonly string[];
|
|
805
837
|
};
|
|
806
838
|
|
|
807
839
|
export type CapabilitySearchTagDefinition = {
|
|
@@ -855,6 +887,7 @@ export type CapabilityOperationPresentationTranslation = {
|
|
|
855
887
|
};
|
|
856
888
|
|
|
857
889
|
export type CapabilityPresentationTranslation = {
|
|
890
|
+
events?: Readonly<Record<string, { title?: string; description?: string }>>;
|
|
858
891
|
types?: Readonly<Record<string, { title?: string; description?: string }>>;
|
|
859
892
|
queries?: Readonly<Record<string, CapabilityOperationPresentationTranslation>>;
|
|
860
893
|
actions?: Readonly<Record<string, CapabilityOperationPresentationTranslation>>;
|
|
@@ -956,13 +989,27 @@ export type CapabilityDefinitions = {
|
|
|
956
989
|
commands?: CapabilityDefinitionCatalog<CapabilityCommandDefinition>;
|
|
957
990
|
fileProvider?: CapabilityFileProviderDeclaration;
|
|
958
991
|
calendarSource?: CapabilityCalendarSourceDeclaration;
|
|
992
|
+
events?: CapabilityDefinitionCatalog<AppEventDefinition>;
|
|
993
|
+
subscribes?: CapabilityDefinitionCatalog<AppEventSubscriptionDefinition>;
|
|
959
994
|
};
|
|
960
995
|
|
|
961
996
|
/**
|
|
962
997
|
* Declares one app's public capability surface. Runtime compilation happens
|
|
963
998
|
* in defineApp.start(), where the app id is available for namespacing.
|
|
964
999
|
*/
|
|
965
|
-
export
|
|
1000
|
+
export function defineCapabilities<
|
|
1001
|
+
const Schemas extends Readonly<Record<string, z.ZodType>>,
|
|
1002
|
+
const Definitions extends Omit<CapabilityDefinitions, "subscribes">,
|
|
1003
|
+
>(
|
|
1004
|
+
definitions: Definitions & { subscribes: { readonly [Key in keyof Schemas]: AppEventSubscriptionDefinition<Schemas[Key]> } },
|
|
1005
|
+
): Definitions & { subscribes: { readonly [Key in keyof Schemas]: AppEventSubscriptionDefinition<Schemas[Key]> } };
|
|
1006
|
+
export function defineCapabilities<const Definitions extends Omit<CapabilityDefinitions, "subscribes">>(
|
|
1007
|
+
definitions: Definitions & { subscribes?: never },
|
|
1008
|
+
): Definitions;
|
|
1009
|
+
export function defineCapabilities<const Definitions extends CapabilityDefinitions>(definitions: Definitions): Definitions;
|
|
1010
|
+
export function defineCapabilities(definitions: CapabilityDefinitions): CapabilityDefinitions {
|
|
1011
|
+
return definitions;
|
|
1012
|
+
}
|
|
966
1013
|
|
|
967
1014
|
export const CapabilityResourceTypeManifestSchema = z
|
|
968
1015
|
.object({
|
|
@@ -970,6 +1017,7 @@ export const CapabilityResourceTypeManifestSchema = z
|
|
|
970
1017
|
title: z.string().min(1).max(120),
|
|
971
1018
|
description: z.string().min(1).max(500),
|
|
972
1019
|
icon: z.string().min(1).max(120).optional(),
|
|
1020
|
+
color: z.enum(CAPABILITY_RESOURCE_COLORS).optional(),
|
|
973
1021
|
reader: CapabilityLocalIdSchema.optional(),
|
|
974
1022
|
})
|
|
975
1023
|
.strict();
|
|
@@ -1033,6 +1081,41 @@ export const CapabilityCalendarSourceManifestSchema = z
|
|
|
1033
1081
|
})
|
|
1034
1082
|
.strict();
|
|
1035
1083
|
|
|
1084
|
+
export const CapabilityEventManifestSchema = z
|
|
1085
|
+
.object({
|
|
1086
|
+
localId: CapabilityLocalIdSchema,
|
|
1087
|
+
version: z.number().int().positive(),
|
|
1088
|
+
title: z.string().trim().min(1).max(120),
|
|
1089
|
+
description: z.string().trim().min(1).max(1000),
|
|
1090
|
+
subject: CapabilityLocalIdSchema,
|
|
1091
|
+
place: CapabilityLocalIdSchema.optional(),
|
|
1092
|
+
dataSchema: z.record(z.string(), z.unknown()),
|
|
1093
|
+
schemaHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
1094
|
+
automation: z.literal(false).optional(),
|
|
1095
|
+
})
|
|
1096
|
+
.strict();
|
|
1097
|
+
export type CapabilityEventManifest = z.infer<typeof CapabilityEventManifestSchema>;
|
|
1098
|
+
|
|
1099
|
+
export const CapabilitySubscriptionManifestSchema = z
|
|
1100
|
+
.object({
|
|
1101
|
+
type: CapabilityQualifiedIdSchema,
|
|
1102
|
+
version: z.number().int().positive(),
|
|
1103
|
+
dataSchema: z.record(z.string(), z.unknown()),
|
|
1104
|
+
schemaHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
1105
|
+
})
|
|
1106
|
+
.strict();
|
|
1107
|
+
export type CapabilitySubscriptionManifest = z.infer<typeof CapabilitySubscriptionManifestSchema>;
|
|
1108
|
+
export const CapabilityResourcePageManifestSchema = z
|
|
1109
|
+
.object({
|
|
1110
|
+
type: CapabilityLocalIdSchema,
|
|
1111
|
+
path: z
|
|
1112
|
+
.string()
|
|
1113
|
+
.min(1)
|
|
1114
|
+
.max(RESOURCE_PAGE_MAX_LENGTH)
|
|
1115
|
+
.refine((path) => !resourcePageIssue(path), "Invalid resource page pattern"),
|
|
1116
|
+
})
|
|
1117
|
+
.strict();
|
|
1118
|
+
|
|
1036
1119
|
/** Keeps the sent entries this release reads completely. */
|
|
1037
1120
|
const readEntries = <T extends z.ZodType>(values: unknown, entry: T): z.output<T>[] | null => {
|
|
1038
1121
|
// Not a bounded list: leave the value to the manifest schema, which rejects it.
|
|
@@ -1059,6 +1142,8 @@ const readEntries = <T extends z.ZodType>(values: unknown, entry: T): z.output<T
|
|
|
1059
1142
|
* - An unreadable policy or one inconsistent with derived approval, review, or effect flags leaves
|
|
1060
1143
|
* out its Action. Policies for unknown Actions are dropped.
|
|
1061
1144
|
*
|
|
1145
|
+
* Resource pages live at the top level so older releases keep the Type and reader. Unreadable
|
|
1146
|
+
* pages and pages without a readable Type/reader are omitted; live prefix ownership is checked separately.
|
|
1062
1147
|
* Every other entry stays available.
|
|
1063
1148
|
*/
|
|
1064
1149
|
const readableManifest = (value: unknown): unknown => {
|
|
@@ -1069,6 +1154,9 @@ const readableManifest = (value: unknown): unknown => {
|
|
|
1069
1154
|
const actions = readEntries(sent.actions, CapabilityActionManifestSchema);
|
|
1070
1155
|
const commands = readEntries(sent.commands, CapabilityCommandManifestSchema);
|
|
1071
1156
|
if (!types || !queries || !actions || !commands) return value;
|
|
1157
|
+
const events = sent.events === undefined ? undefined : readEntries(sent.events, CapabilityEventManifestSchema);
|
|
1158
|
+
const subscribes = sent.subscribes === undefined ? undefined : readEntries(sent.subscribes, CapabilitySubscriptionManifestSchema);
|
|
1159
|
+
if (events === null || subscribes === null) return value;
|
|
1072
1160
|
const typeIds = new Set(types.map((type) => type.localId));
|
|
1073
1161
|
const readableQueries = queries.filter((query) => query.universalSearch?.scopeTypes?.every((type) => typeIds.has(type)) ?? true);
|
|
1074
1162
|
const queryIds = new Set(readableQueries.map((query) => query.localId));
|
|
@@ -1092,11 +1180,25 @@ const readableManifest = (value: unknown): unknown => {
|
|
|
1092
1180
|
policies[action.localId] = policy;
|
|
1093
1181
|
return true;
|
|
1094
1182
|
});
|
|
1095
|
-
const { calendarSource: sentCalendarSource, fileProvider: sentProvider, policies: _policies, ...rest } = sent;
|
|
1183
|
+
const { calendarSource: sentCalendarSource, fileProvider: sentProvider, policies: _policies, resourcePages: sentPages, ...rest } = sent;
|
|
1096
1184
|
const provider = CapabilityFileProviderManifestSchema.safeParse(sentProvider);
|
|
1097
1185
|
const calendar = CapabilityCalendarSourceManifestSchema.safeParse(sentCalendarSource);
|
|
1186
|
+
const pageCounts = new Map<string, number>();
|
|
1187
|
+
const resourcePages =
|
|
1188
|
+
Array.isArray(sentPages) && sentPages.length <= MAX_MANIFEST_ENTRIES * RESOURCE_PAGES_PER_TYPE
|
|
1189
|
+
? sentPages.flatMap((value) => {
|
|
1190
|
+
const page = CapabilityResourcePageManifestSchema.safeParse(value);
|
|
1191
|
+
if (!page.success) return [];
|
|
1192
|
+
const type = types.find((type) => type.localId === page.data.type);
|
|
1193
|
+
const count = pageCounts.get(page.data.type) ?? 0;
|
|
1194
|
+
if (!type?.reader || !queryIds.has(type.reader) || count >= RESOURCE_PAGES_PER_TYPE) return [];
|
|
1195
|
+
pageCounts.set(page.data.type, count + 1);
|
|
1196
|
+
return [page.data];
|
|
1197
|
+
})
|
|
1198
|
+
: [];
|
|
1098
1199
|
return {
|
|
1099
1200
|
...rest,
|
|
1201
|
+
...(sentPages !== undefined ? { resourcePages } : {}),
|
|
1100
1202
|
...(provider.success ? { fileProvider: provider.data } : {}),
|
|
1101
1203
|
...(calendar.success ? { calendarSource: calendar.data } : {}),
|
|
1102
1204
|
types: types.map((type) => {
|
|
@@ -1108,6 +1210,16 @@ const readableManifest = (value: unknown): unknown => {
|
|
|
1108
1210
|
actions: readableActions,
|
|
1109
1211
|
...(sentPolicies !== undefined ? { policies } : {}),
|
|
1110
1212
|
commands,
|
|
1213
|
+
...(events
|
|
1214
|
+
? {
|
|
1215
|
+
events: events.filter(
|
|
1216
|
+
(event) =>
|
|
1217
|
+
typeIds.has(event.subject) &&
|
|
1218
|
+
(!event.place || types.some((type) => type.localId === event.place && type.reader && queryIds.has(type.reader))),
|
|
1219
|
+
),
|
|
1220
|
+
}
|
|
1221
|
+
: {}),
|
|
1222
|
+
...(subscribes ? { subscribes } : {}),
|
|
1111
1223
|
};
|
|
1112
1224
|
};
|
|
1113
1225
|
|
|
@@ -1129,6 +1241,12 @@ export const CapabilityManifestSchema = z.preprocess(
|
|
|
1129
1241
|
fileProvider: CapabilityFileProviderManifestSchema.optional(),
|
|
1130
1242
|
calendarSource: CapabilityCalendarSourceManifestSchema.optional(),
|
|
1131
1243
|
policies: z.record(CapabilityLocalIdSchema, CapabilityActionPolicySchema).optional(),
|
|
1244
|
+
events: z.array(CapabilityEventManifestSchema).max(MAX_MANIFEST_ENTRIES).optional(),
|
|
1245
|
+
subscribes: z.array(CapabilitySubscriptionManifestSchema).max(MAX_MANIFEST_ENTRIES).optional(),
|
|
1246
|
+
resourcePages: z
|
|
1247
|
+
.array(CapabilityResourcePageManifestSchema)
|
|
1248
|
+
.max(MAX_MANIFEST_ENTRIES * RESOURCE_PAGES_PER_TYPE)
|
|
1249
|
+
.optional(),
|
|
1132
1250
|
}),
|
|
1133
1251
|
);
|
|
1134
1252
|
|
|
@@ -1153,12 +1271,34 @@ export const resolveCapabilityResourceReader = (manifest: CapabilityManifest, re
|
|
|
1153
1271
|
};
|
|
1154
1272
|
|
|
1155
1273
|
/** Like the manifest, catalog entries, pages, and their pagination ignore fields that a newer Cloud release added. */
|
|
1274
|
+
export const CapabilityEventCatalogEntrySchema = z.object({
|
|
1275
|
+
type: CapabilityQualifiedIdSchema,
|
|
1276
|
+
version: z.number().int().positive(),
|
|
1277
|
+
title: z.string().min(1).max(120),
|
|
1278
|
+
description: z.string().min(1).max(1000),
|
|
1279
|
+
subject: CapabilityQualifiedIdSchema,
|
|
1280
|
+
place: CapabilityQualifiedIdSchema.nullable(),
|
|
1281
|
+
automation: z.boolean(),
|
|
1282
|
+
dataSchema: z.record(z.string(), z.unknown()),
|
|
1283
|
+
subscribers: z.array(
|
|
1284
|
+
z.object({
|
|
1285
|
+
appId: CapabilityAppIdSchema,
|
|
1286
|
+
subscriptionId: z.string(),
|
|
1287
|
+
state: z.enum(["active", "blocked", "cancelled"]),
|
|
1288
|
+
reason: z.string().nullable(),
|
|
1289
|
+
firstSeenAt: z.iso.datetime({ offset: true }).optional(),
|
|
1290
|
+
}),
|
|
1291
|
+
),
|
|
1292
|
+
});
|
|
1293
|
+
export type CapabilityEventCatalogEntry = z.infer<typeof CapabilityEventCatalogEntrySchema>;
|
|
1294
|
+
|
|
1156
1295
|
export const CapabilityCatalogAppSchema = z.object({
|
|
1157
1296
|
appId: CapabilityAppIdSchema,
|
|
1158
1297
|
appName: z.string().min(1).max(200),
|
|
1159
1298
|
appIcon: z.string().min(1).max(120),
|
|
1160
1299
|
appDescription: z.string().max(1000),
|
|
1161
1300
|
manifest: CapabilityManifestSchema,
|
|
1301
|
+
events: z.array(CapabilityEventCatalogEntrySchema).max(MAX_MANIFEST_ENTRIES).optional(),
|
|
1162
1302
|
});
|
|
1163
1303
|
|
|
1164
1304
|
export const CapabilityCatalogSchema = z.object({
|
package/src/contracts/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./account-categories";
|
|
|
2
2
|
export * from "./announcements";
|
|
3
3
|
export * from "./app";
|
|
4
4
|
export * from "./app-approval";
|
|
5
|
+
export * from "./app-details";
|
|
5
6
|
export * from "./avatar";
|
|
6
7
|
export * from "./calendar-source";
|
|
7
8
|
export * from "./calendar-view";
|
|
@@ -27,6 +28,7 @@ export { RailAccessSchema, type RailAdminEntry, RailAdminInputSchema, RailAdminS
|
|
|
27
28
|
export type { RailShortcut } from "./rail-preferences";
|
|
28
29
|
export * from "./registry";
|
|
29
30
|
export * from "./resource-clipboard";
|
|
31
|
+
export { type ResolvedCloudResourceUrl, ResolvedCloudResourceUrlSchema } from "./resource-resolution";
|
|
30
32
|
export * from "./seen-hints";
|
|
31
33
|
export * from "./shared";
|
|
32
34
|
export * from "./user-notifications";
|
|
@@ -37,6 +37,8 @@ export type NotificationPresentation = {
|
|
|
37
37
|
group?: string;
|
|
38
38
|
/** Non-negative safe integer for the app badge; zero clears it. Browser-only. */
|
|
39
39
|
badge?: number;
|
|
40
|
+
/** `false` shows or replaces the browser notification without sound or vibration. Defaults to `true`. Browser-only. */
|
|
41
|
+
alert?: boolean;
|
|
40
42
|
/** Same-origin absolute path. Validated again before persistence and navigation. */
|
|
41
43
|
targetHref?: `/${string}`;
|
|
42
44
|
};
|
|
@@ -55,15 +57,18 @@ export type NotificationRenderContext = {
|
|
|
55
57
|
export type NotificationDeliveryPolicy = {
|
|
56
58
|
recommended?: readonly NotificationChannelId[];
|
|
57
59
|
required?: readonly NotificationChannelId[];
|
|
60
|
+
/** Channels this kind never prepares for delivery, even when a saved preference selects them. */
|
|
61
|
+
excluded?: readonly NotificationChannelId[];
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
export type NotificationDefinitionPresentationTranslation = {
|
|
61
65
|
label?: string;
|
|
62
66
|
description?: string;
|
|
67
|
+
excludedReason?: string;
|
|
63
68
|
};
|
|
64
69
|
|
|
65
70
|
export type NotificationDefinitionPresentationCatalog = {
|
|
66
|
-
/** Locale of the
|
|
71
|
+
/** Locale of the base label, description, and optional excluded reason. */
|
|
67
72
|
baseLocale: string;
|
|
68
73
|
/** Partial presentation overlays with exact -> ancestor -> base fallback. */
|
|
69
74
|
translations: Readonly<Record<string, NotificationDefinitionPresentationTranslation>>;
|
|
@@ -73,6 +78,8 @@ export type NotificationDefinitionInput<R extends NotificationRecipientKind, S e
|
|
|
73
78
|
recipient: R;
|
|
74
79
|
label: string;
|
|
75
80
|
description: string;
|
|
81
|
+
/** Explanation shown beside excluded channels; required when delivery.excluded is non-empty. */
|
|
82
|
+
excludedReason?: string;
|
|
76
83
|
presentation?: NotificationDefinitionPresentationCatalog;
|
|
77
84
|
data: S;
|
|
78
85
|
delivery?: NotificationDeliveryPolicy;
|
|
@@ -92,6 +99,7 @@ type AnyNotificationDefinition = Readonly<{
|
|
|
92
99
|
recipient: NotificationRecipientKind;
|
|
93
100
|
label: string;
|
|
94
101
|
description: string;
|
|
102
|
+
excludedReason?: string;
|
|
95
103
|
presentation?: NotificationDefinitionPresentationCatalog;
|
|
96
104
|
data: ZodType;
|
|
97
105
|
delivery?: NotificationDeliveryPolicy;
|
|
@@ -165,6 +173,9 @@ const compileDefinitionPresentation = (
|
|
|
165
173
|
...(translation.description !== undefined
|
|
166
174
|
? { description: requiredPresentationText(translation.description, `${canonical}.description`) }
|
|
167
175
|
: {}),
|
|
176
|
+
...(translation.excludedReason !== undefined
|
|
177
|
+
? { excludedReason: requiredPresentationText(translation.excludedReason, `${canonical}.excludedReason`) }
|
|
178
|
+
: {}),
|
|
168
179
|
});
|
|
169
180
|
}
|
|
170
181
|
return Object.freeze({ baseLocale, translations: Object.freeze(translations) });
|
|
@@ -174,20 +185,23 @@ export const resolveNotificationDefinitionPresentation = (
|
|
|
174
185
|
definition: {
|
|
175
186
|
label: string;
|
|
176
187
|
description: string;
|
|
188
|
+
excludedReason?: string;
|
|
177
189
|
presentation?: NotificationDefinitionPresentationCatalog | null;
|
|
178
190
|
},
|
|
179
191
|
requestedLocale?: string | null,
|
|
180
|
-
): { label: string; description: string } => {
|
|
192
|
+
): { label: string; description: string; excludedReason?: string } => {
|
|
181
193
|
const catalog = definition.presentation;
|
|
182
|
-
|
|
194
|
+
const base = {
|
|
195
|
+
label: definition.label,
|
|
196
|
+
description: definition.description,
|
|
197
|
+
...(definition.excludedReason !== undefined ? { excludedReason: definition.excludedReason } : {}),
|
|
198
|
+
};
|
|
199
|
+
if (!catalog || !requestedLocale) return base;
|
|
183
200
|
const translations = new Map(Object.entries(catalog.translations));
|
|
184
201
|
return localeFallbackChain(requestedLocale, catalog.baseLocale)
|
|
185
202
|
.filter((locale) => locale !== catalog.baseLocale)
|
|
186
203
|
.reverse()
|
|
187
|
-
.reduce((resolved, locale) => ({ ...resolved, ...translations.get(locale) }),
|
|
188
|
-
label: definition.label,
|
|
189
|
-
description: definition.description,
|
|
190
|
-
});
|
|
204
|
+
.reduce((resolved, locale) => ({ ...resolved, ...translations.get(locale) }), base);
|
|
191
205
|
};
|
|
192
206
|
|
|
193
207
|
export const notification = <const R extends NotificationRecipientKind, const S extends ZodType>(
|
|
@@ -200,13 +214,20 @@ export const notification = <const R extends NotificationRecipientKind, const S
|
|
|
200
214
|
|
|
201
215
|
const recommended = [...(input.delivery?.recommended ?? [])];
|
|
202
216
|
const required = [...(input.delivery?.required ?? [])];
|
|
203
|
-
|
|
217
|
+
const excluded = [...(input.delivery?.excluded ?? [])];
|
|
218
|
+
const excludedReason = input.excludedReason === undefined ? undefined : requiredPresentationText(input.excludedReason, "excludedReason");
|
|
219
|
+
if (excluded.length > 0 && !excludedReason) throw new Error(`Notification "${label}" excludedReason is required for excluded channels`);
|
|
220
|
+
if (hasDuplicates(recommended) || hasDuplicates(required) || hasDuplicates(excluded)) {
|
|
204
221
|
throw new Error(`Notification "${label}" contains duplicate delivery channels`);
|
|
205
222
|
}
|
|
206
223
|
const overlappingChannel = recommended.find((channel) => required.includes(channel));
|
|
207
224
|
if (overlappingChannel) {
|
|
208
225
|
throw new Error(`Notification "${label}" cannot recommend and require the "${overlappingChannel}" channel`);
|
|
209
226
|
}
|
|
227
|
+
const excludedOverlap = excluded.find((channel) => recommended.includes(channel) || required.includes(channel));
|
|
228
|
+
if (excludedOverlap) {
|
|
229
|
+
throw new Error(`Notification "${label}" cannot exclude the recommended or required "${excludedOverlap}" channel`);
|
|
230
|
+
}
|
|
210
231
|
if (input.recipient === "email" && !required.includes("email")) {
|
|
211
232
|
throw new Error(`Email-recipient notification "${label}" must require the email channel`);
|
|
212
233
|
}
|
|
@@ -216,8 +237,14 @@ export const notification = <const R extends NotificationRecipientKind, const S
|
|
|
216
237
|
...input,
|
|
217
238
|
label,
|
|
218
239
|
description,
|
|
240
|
+
...(excludedReason !== undefined ? { excludedReason } : {}),
|
|
219
241
|
...(presentation ? { presentation } : {}),
|
|
220
|
-
delivery: Object.freeze({
|
|
242
|
+
delivery: Object.freeze({
|
|
243
|
+
recommended: Object.freeze(recommended),
|
|
244
|
+
required: Object.freeze(required),
|
|
245
|
+
// Only kinds that exclude a channel carry the field, so every other definition keeps its shape.
|
|
246
|
+
...(excluded.length > 0 ? { excluded: Object.freeze(excluded) } : {}),
|
|
247
|
+
}),
|
|
221
248
|
[notificationDefinitionMarker]: true,
|
|
222
249
|
};
|
|
223
250
|
return Object.freeze(definition);
|
package/src/contracts/pwa.ts
CHANGED
|
@@ -90,7 +90,7 @@ const Code = z.string().regex(/^\d{6}$/);
|
|
|
90
90
|
|
|
91
91
|
export const PwaPlatformSchema = z.enum(["ios", "android", "other"]);
|
|
92
92
|
export type PwaPlatform = z.infer<typeof PwaPlatformSchema>;
|
|
93
|
-
export const PwaPairingStateSchema = z.enum(["pending", "claimed", "confirmed", "completed", "cancelled"]);
|
|
93
|
+
export const PwaPairingStateSchema = z.enum(["pending", "claimed", "awaiting_device", "confirmed", "completed", "rejected", "cancelled"]);
|
|
94
94
|
export type PwaPairingState = z.infer<typeof PwaPairingStateSchema>;
|
|
95
95
|
/** States of `/pwa/` the launch bounce leads to when it cannot renew. */
|
|
96
96
|
export const PwaLaunchStateSchema = z.enum(["new", "ended", "blocked", "unavailable"]);
|
|
@@ -146,8 +146,24 @@ export const PwaDeviceListSchema = z.object({ items: z.array(PwaDeviceViewSchema
|
|
|
146
146
|
export const PwaClaimSchema = z.object({ secret: z.string().regex(SECRET_PATTERN), platform: PwaPlatformSchema }).strict();
|
|
147
147
|
const Account = z.object({ name: z.string() }).strict();
|
|
148
148
|
export const PwaClaimResultSchema = z.object({ code: Code, account: Account, expiresAt: DateTime }).strict();
|
|
149
|
+
export const PwaDeviceDecisionSchema = z
|
|
150
|
+
.object({
|
|
151
|
+
confirmationToken: z.string().regex(SECRET_PATTERN),
|
|
152
|
+
decision: z.enum(["confirm", "reject"]),
|
|
153
|
+
})
|
|
154
|
+
.strict();
|
|
149
155
|
export const PwaCompleteResultSchema = z.discriminatedUnion("state", [
|
|
150
156
|
z.object({ state: z.literal("paired") }).strict(),
|
|
157
|
+
z.object({ state: z.literal("rejected") }).strict(),
|
|
158
|
+
z
|
|
159
|
+
.object({
|
|
160
|
+
state: z.literal("confirmation_required"),
|
|
161
|
+
account: Account,
|
|
162
|
+
administrator: Account,
|
|
163
|
+
confirmationToken: z.string().regex(SECRET_PATTERN),
|
|
164
|
+
expiresAt: DateTime,
|
|
165
|
+
})
|
|
166
|
+
.strict(),
|
|
151
167
|
z.object({ state: z.literal("waiting"), code: Code, account: Account, expiresAt: DateTime }).strict(),
|
|
152
168
|
]);
|
|
153
169
|
export const PwaRenewResultSchema = z
|
|
@@ -25,6 +25,10 @@ export type AppRegistryNav = {
|
|
|
25
25
|
export type AppRegistryPwaPart = {
|
|
26
26
|
href: string;
|
|
27
27
|
requiresRoles?: Role[];
|
|
28
|
+
/** Count route for the part's sidebar entry; older readers ignore it. */
|
|
29
|
+
badge?: string;
|
|
30
|
+
/** Summary route for the part's line on Start; older readers ignore it. */
|
|
31
|
+
summary?: string;
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
export type AppRegistryCapabilitySummary = {
|
|
@@ -82,6 +86,8 @@ export type AppRegistryEntry = {
|
|
|
82
86
|
access?: AppAccessRule;
|
|
83
87
|
platformPermissions?: readonly PlatformPermission[];
|
|
84
88
|
id: string;
|
|
89
|
+
/** Shared object-store package; omitted means the app ID. */
|
|
90
|
+
package?: string;
|
|
85
91
|
name: string;
|
|
86
92
|
icon: string;
|
|
87
93
|
description: string;
|