@k2b/cloud 0.42.0 → 0.44.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.
Files changed (352) hide show
  1. package/package.json +13 -5
  2. package/scripts/app-favicon.ts +7 -6
  3. package/scripts/build.ts +3 -0
  4. package/scripts/installable-assets.ts +61 -0
  5. package/scripts/preload.ts +3 -0
  6. package/src/_internal/app-access-path.ts +12 -0
  7. package/src/_internal/app-access-request.ts +13 -0
  8. package/src/_internal/app-access-resources.ts +141 -0
  9. package/src/_internal/app-event-catalog.ts +40 -0
  10. package/src/_internal/app-event-schemas.ts +127 -0
  11. package/src/_internal/application-package.ts +40 -0
  12. package/src/_internal/built-in-app-ids.json +27 -0
  13. package/src/_internal/capabilities.ts +185 -12
  14. package/src/_internal/capability-origin.ts +4 -0
  15. package/src/_internal/capability-streams.ts +10 -5
  16. package/src/_internal/define-app.ts +159 -45
  17. package/src/_internal/installable-assets.ts +90 -0
  18. package/src/_internal/installable-head.ts +22 -0
  19. package/src/_internal/object-store-area.ts +9 -0
  20. package/src/_internal/object-store-config.ts +101 -0
  21. package/src/_internal/object-store-key.ts +14 -0
  22. package/src/_internal/page-responses.ts +34 -3
  23. package/src/_internal/process-identity.ts +7 -2
  24. package/src/_internal/registry-validation.ts +15 -1
  25. package/src/_internal/registry.ts +44 -1
  26. package/src/_internal/runtime-context.ts +9 -1
  27. package/src/_internal/runtime-watcher.ts +8 -0
  28. package/src/_internal/status-preserving-ssr.ts +12 -1
  29. package/src/access/AppAccessEditor.browser-fixture.ts +429 -0
  30. package/src/access/AppAccessEditor.browser-frame.tsx +68 -0
  31. package/src/access/AppAccessEditor.browser-harness.tsx +65 -0
  32. package/src/access/AppAccessEditor.tsx +380 -0
  33. package/src/access/PermissionEditor.browser-harness.tsx +22 -2
  34. package/src/access/PermissionEditor.tsx +386 -77
  35. package/src/access/PrincipalAvatar.tsx +63 -0
  36. package/src/access/PrincipalPicker.tsx +29 -8
  37. package/src/access/ResourceApiKeys.tsx +3 -4
  38. package/src/access/app-access-adapter.ts +51 -0
  39. package/src/access/app-access-messages.ts +172 -0
  40. package/src/access/messages.ts +14 -0
  41. package/src/access/ui.ts +4 -1
  42. package/src/account/Avatar.tsx +21 -3
  43. package/src/account/ProfilePictureDialog.tsx +699 -0
  44. package/src/account/profile-picture-messages.ts +269 -0
  45. package/src/account/ui.ts +8 -1
  46. package/src/ai/admin.ts +3 -0
  47. package/src/ai/app-frame.ts +12 -0
  48. package/src/ai/application-tasks.ts +101 -0
  49. package/src/ai/assistant-models.ts +126 -64
  50. package/src/ai/capabilities.ts +19 -4
  51. package/src/ai/capability-execution.ts +5 -2
  52. package/src/ai/chart-block.ts +63 -30
  53. package/src/ai/chat/chart-block.tsx +12 -3
  54. package/src/ai/chat/composer-adapter.ts +8 -7
  55. package/src/ai/chat/message-actions.tsx +1 -1
  56. package/src/ai/chat/message-utils.ts +2 -2
  57. package/src/ai/chat/presentation.tsx +22 -1
  58. package/src/ai/chat-quotas.ts +5 -4
  59. package/src/ai/chat-tasks.ts +2 -2
  60. package/src/ai/client/controller.ts +11 -10
  61. package/src/ai/client/projection.ts +3 -3
  62. package/src/ai/code-capability-routes.ts +1 -0
  63. package/src/ai/compaction.ts +3 -3
  64. package/src/ai/credentials.ts +13 -114
  65. package/src/ai/default-tool-contracts.ts +4 -3
  66. package/src/ai/dictation-runtime.ts +4 -4
  67. package/src/ai/enrich.ts +2 -2
  68. package/src/ai/executor.ts +38 -27
  69. package/src/ai/http.ts +17 -29
  70. package/src/ai/index.ts +11 -5
  71. package/src/ai/inference-calls.ts +6 -4
  72. package/src/ai/maintenance.ts +1 -1
  73. package/src/ai/memory-learning-runs.ts +5 -5
  74. package/src/ai/memory-learning.ts +3 -3
  75. package/src/ai/message-queue.ts +1 -2
  76. package/src/ai/migrate.ts +75 -42
  77. package/src/ai/model-access-migrate.ts +3 -17
  78. package/src/ai/model-access.ts +44 -85
  79. package/src/ai/model-card.tsx +134 -0
  80. package/src/ai/model-catalog.ts +359 -0
  81. package/src/ai/model-chooser-messages.ts +88 -0
  82. package/src/ai/model-chooser.tsx +577 -0
  83. package/src/ai/model-delete.ts +234 -0
  84. package/src/ai/model-fixture.ts +39 -0
  85. package/src/ai/model-icon.tsx +36 -0
  86. package/src/ai/model-overview.ts +53 -0
  87. package/src/ai/model-usage.ts +45 -0
  88. package/src/ai/openai-api-messages.ts +0 -4
  89. package/src/ai/openai-api-slots.ts +10 -2
  90. package/src/ai/openai-api.ts +83 -16
  91. package/src/ai/personal-agent.ts +1 -1
  92. package/src/ai/prefs.ts +12 -10
  93. package/src/ai/projects-routes.ts +2 -1
  94. package/src/ai/projects.ts +13 -12
  95. package/src/ai/protocol.ts +2 -2
  96. package/src/ai/provider-connections.ts +161 -0
  97. package/src/ai/provider-errors.ts +138 -0
  98. package/src/ai/provider-fetch.ts +4 -0
  99. package/src/ai/provider-retry.ts +1 -1
  100. package/src/ai/provider.ts +2 -1
  101. package/src/ai/quota-limits.ts +36 -0
  102. package/src/ai/quota-provider.ts +11 -2
  103. package/src/ai/quota-report.ts +7 -4
  104. package/src/ai/quotas-migrate.ts +1 -0
  105. package/src/ai/quotas.ts +19 -8
  106. package/src/ai/recent-resources.ts +6 -0
  107. package/src/ai/request-headers.ts +14 -87
  108. package/src/ai/resource-refs.ts +6 -2
  109. package/src/ai/routes.ts +17 -18
  110. package/src/ai/runtime.ts +50 -22
  111. package/src/ai/settings.ts +93 -580
  112. package/src/ai/store.ts +45 -23
  113. package/src/ai/stream.ts +1 -1
  114. package/src/ai/structured-runs.ts +2 -2
  115. package/src/ai/structured.ts +29 -46
  116. package/src/ai/task-contracts.ts +25 -5
  117. package/src/ai/task-execution.ts +15 -6
  118. package/src/ai/task-model.ts +9 -0
  119. package/src/ai/transcription.ts +37 -21
  120. package/src/ai/turn-failure.ts +24 -2
  121. package/src/ai/types.ts +50 -62
  122. package/src/ai/ui.tsx +13 -0
  123. package/src/ai/usage.ts +8 -8
  124. package/src/ai/validate.ts +8 -7
  125. package/src/ai/view-block.ts +2 -2
  126. package/src/api/accounts-entities.ts +40 -16
  127. package/src/api/admin-ai-quotas.ts +34 -56
  128. package/src/api/admin-ai-usage.ts +1 -1
  129. package/src/api/admin-ai.ts +173 -0
  130. package/src/api/admin-core-settings.ts +68 -244
  131. package/src/api/admin-directory-shares.ts +55 -0
  132. package/src/api/admin-identity.ts +81 -2
  133. package/src/api/app-access.ts +85 -0
  134. package/src/api/app-approval.ts +3 -1
  135. package/src/api/app-event-delivery.ts +51 -0
  136. package/src/api/app-events.ts +123 -0
  137. package/src/api/app-resource-access.ts +48 -0
  138. package/src/api/auth/account-mismatch.ts +24 -0
  139. package/src/api/auth.ts +26 -3
  140. package/src/api/capabilities.ts +127 -15
  141. package/src/api/capability-streams.ts +2 -5
  142. package/src/api/identity-child-invocation.ts +145 -0
  143. package/src/api/index.ts +11 -2
  144. package/src/api/mcp.ts +180 -5
  145. package/src/api/me.ts +38 -17
  146. package/src/api/pwa-phone.ts +87 -48
  147. package/src/api/pwa.ts +34 -8
  148. package/src/api/resource-links.ts +83 -6
  149. package/src/api/resource-resolve.ts +50 -0
  150. package/src/api/search/schemas.ts +3 -1
  151. package/src/api/search.ts +9 -7
  152. package/src/api/widgets.ts +1 -0
  153. package/src/app-pwa/authority.ts +75 -0
  154. package/src/app-pwa/contracts.ts +177 -0
  155. package/src/app-pwa/core-query-error.ts +20 -0
  156. package/src/app-pwa/index.ts +23 -0
  157. package/src/app-pwa/protocol.ts +27 -0
  158. package/src/app-pwa/schema.ts +59 -0
  159. package/src/app-pwa/service.ts +861 -0
  160. package/src/app-pwa/transaction.ts +338 -0
  161. package/src/app-pwa/transport-input.ts +57 -0
  162. package/src/app-pwa/transport.ts +235 -0
  163. package/src/approvals/messages.ts +2 -2
  164. package/src/browser/app-access-recovery.ts +44 -0
  165. package/src/browser/app-session.ts +14 -3
  166. package/src/browser/cloud-id.ts +86 -0
  167. package/src/browser/live-websocket.ts +5 -0
  168. package/src/browser/pwa-drafts.ts +57 -0
  169. package/src/browser/resource-clipboard.ts +4 -0
  170. package/src/browser/resource-links.ts +52 -0
  171. package/src/browser/resource-picker.tsx +3 -2
  172. package/src/capabilities/calendar-sources.ts +302 -0
  173. package/src/capabilities/executions.ts +10 -3
  174. package/src/capabilities/migrate.ts +4 -0
  175. package/src/capabilities/nested.ts +43 -0
  176. package/src/capabilities/server-invoke.ts +202 -0
  177. package/src/capabilities/server.ts +26 -187
  178. package/src/capabilities/testing.ts +5 -2
  179. package/src/cli/access.ts +6 -5
  180. package/src/cli/account.ts +57 -0
  181. package/src/cli/admin/agents.ts +66 -0
  182. package/src/cli/admin/ai-models.ts +199 -0
  183. package/src/cli/admin/ai-quotas.ts +6 -102
  184. package/src/cli/admin/ai-usage.ts +4 -4
  185. package/src/cli/admin/app-credentials.ts +5 -3
  186. package/src/cli/admin/index.ts +7 -3
  187. package/src/cli/app-access.ts +60 -0
  188. package/src/cli/apps.ts +89 -26
  189. package/src/cli/avatar.ts +64 -0
  190. package/src/cli/capabilities.ts +22 -1
  191. package/src/cli/events.ts +320 -0
  192. package/src/cli/index.ts +1 -1
  193. package/src/config/env.ts +40 -0
  194. package/src/contracts/app-access.ts +52 -5
  195. package/src/contracts/app-approval.ts +8 -1
  196. package/src/contracts/app-details.ts +89 -0
  197. package/src/contracts/app-events.ts +51 -0
  198. package/src/contracts/app.ts +24 -5
  199. package/src/contracts/calendar-source.ts +274 -0
  200. package/src/contracts/calendar-view.ts +35 -0
  201. package/src/contracts/capabilities.ts +164 -3
  202. package/src/contracts/geo-admin.ts +2 -0
  203. package/src/contracts/geo-route.ts +0 -11
  204. package/src/contracts/geo.ts +14 -2
  205. package/src/contracts/index.ts +6 -0
  206. package/src/contracts/installable.ts +40 -0
  207. package/src/contracts/notification-types.ts +36 -9
  208. package/src/contracts/profile.ts +32 -0
  209. package/src/contracts/pwa-builds.ts +14 -0
  210. package/src/contracts/pwa-location.ts +38 -0
  211. package/src/contracts/pwa.ts +24 -3
  212. package/src/contracts/registry.ts +8 -0
  213. package/src/contracts/resource-clipboard.ts +35 -0
  214. package/src/contracts/resource-pages.ts +140 -0
  215. package/src/contracts/resource-resolution.ts +13 -0
  216. package/src/contracts/shared.ts +4 -0
  217. package/src/contracts/user-notifications.ts +2 -0
  218. package/src/events/app-event-delivery.ts +100 -0
  219. package/src/events/app-events.testing.ts +65 -0
  220. package/src/events/app-events.ts +134 -0
  221. package/src/events/core-user-created.ts +24 -0
  222. package/src/events/index.ts +9 -0
  223. package/src/events/testing.ts +2 -0
  224. package/src/index.ts +2 -1
  225. package/src/server/api/respond.ts +8 -3
  226. package/src/server/index.ts +1 -0
  227. package/src/server/middleware/app-access.ts +7 -6
  228. package/src/server/middleware/auth.ts +168 -66
  229. package/src/server/middleware/index.ts +1 -0
  230. package/src/server/middleware/invocation.ts +4 -2
  231. package/src/server/middleware/workload.ts +3 -3
  232. package/src/server/oauth-scope.ts +10 -0
  233. package/src/server/public-storage.ts +106 -0
  234. package/src/server/services/access.ts +8 -0
  235. package/src/services/accounts/app.ts +35 -3
  236. package/src/services/accounts/avatar.ts +342 -35
  237. package/src/services/accounts/base-group.ts +4 -5
  238. package/src/services/accounts/directory-shares.ts +157 -0
  239. package/src/services/accounts/entities.ts +50 -6
  240. package/src/services/accounts/groups.ts +3 -1
  241. package/src/services/accounts/identities.ts +64 -12
  242. package/src/services/accounts/local-groups.ts +1 -1
  243. package/src/services/accounts/users.ts +23 -15
  244. package/src/services/app-access-management.ts +519 -0
  245. package/src/services/app-access.ts +25 -7
  246. package/src/services/app-approval.ts +15 -4
  247. package/src/services/app-details.ts +224 -0
  248. package/src/services/app-discovery.ts +2 -1
  249. package/src/services/app-event-shapes.ts +139 -0
  250. package/src/services/app-events/accept.ts +116 -0
  251. package/src/services/app-events/admin.ts +250 -0
  252. package/src/services/app-events/declarations.ts +187 -0
  253. package/src/services/app-events/deliver.ts +218 -0
  254. package/src/services/app-events.ts +92 -0
  255. package/src/services/config-conflict.ts +11 -0
  256. package/src/services/core-user-events.ts +1 -0
  257. package/src/services/freeipa-config.ts +9 -0
  258. package/src/services/geo/admin.ts +4 -1
  259. package/src/services/geo/ledger.ts +47 -14
  260. package/src/services/geo/route-chain.ts +40 -18
  261. package/src/services/geo/route-estimate.ts +19 -0
  262. package/src/services/geo/route-plan.ts +0 -2
  263. package/src/services/geo/route-service.ts +57 -39
  264. package/src/services/geo/service.ts +51 -25
  265. package/src/services/identity/core-origin.ts +12 -0
  266. package/src/services/identity/index.ts +2 -1
  267. package/src/services/identity/invocation-actor.ts +50 -2
  268. package/src/services/identity/invocation-authority.ts +38 -1
  269. package/src/services/identity/invocation-token.ts +255 -50
  270. package/src/services/identity/issue-invocation.ts +9 -1
  271. package/src/services/identity/models-only-auth.ts +13 -4
  272. package/src/services/identity/nested-invocation.ts +128 -0
  273. package/src/services/image-metadata.ts +65 -1
  274. package/src/services/index.ts +24 -1
  275. package/src/services/ipa/sync.ts +6 -2
  276. package/src/services/ipa/users.ts +4 -1
  277. package/src/services/mandates/policy.ts +19 -12
  278. package/src/services/notifications/browser.ts +4 -0
  279. package/src/services/notifications/catalog.ts +5 -2
  280. package/src/services/notifications/platform.ts +7 -2
  281. package/src/services/notifications/user.ts +18 -7
  282. package/src/services/oauth-tokens.ts +28 -2
  283. package/src/services/object-store.ts +217 -0
  284. package/src/services/providers/local/users.ts +5 -0
  285. package/src/services/pwa-devices.ts +147 -37
  286. package/src/services/service-account-credentials.ts +5 -2
  287. package/src/services/session/index.ts +14 -0
  288. package/src/services/settings/core-settings.ts +91 -47
  289. package/src/services/settings/index.ts +8 -0
  290. package/src/services/settings/versions.ts +26 -0
  291. package/src/shared/ai-models.ts +274 -0
  292. package/src/shared/ai-quotas.ts +2 -2
  293. package/src/shared/ai-usage.ts +2 -2
  294. package/src/shared/index.ts +2 -0
  295. package/src/shared-text/documents.ts +25 -8
  296. package/src/shared-text/log.ts +1 -1
  297. package/src/shared-text/text-diff.ts +75 -0
  298. package/src/ssr/AdminArea.tsx +131 -0
  299. package/src/ssr/AdminLayout.tsx +11 -3
  300. package/src/ssr/AdminSidebar.tsx +7 -7
  301. package/src/ssr/AppAccessDenied.tsx +114 -0
  302. package/src/ssr/AppBadge.tsx +14 -0
  303. package/src/ssr/AppLaunchpadPanel.tsx +1 -1
  304. package/src/ssr/AppPwaRuntime.island.tsx +82 -0
  305. package/src/ssr/CloudShell.island.tsx +25 -4
  306. package/src/ssr/Layout.tsx +15 -7
  307. package/src/ssr/PwaLayout.tsx +73 -8
  308. package/src/ssr/PwaPartCount.island.tsx +23 -0
  309. package/src/ssr/PwaRuntime.island.tsx +85 -6
  310. package/src/ssr/SettingsSaveOutcome.tsx +58 -0
  311. package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
  312. package/src/ssr/ShellRuntime.island.tsx +5 -0
  313. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
  314. package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +1 -1
  315. package/src/ssr/admin-active-link.ts +16 -0
  316. package/src/ssr/admin-navigation.ts +26 -20
  317. package/src/ssr/app-badges.ts +31 -7
  318. package/src/ssr/app-pwa-document.tsx +16 -0
  319. package/src/ssr/app-pwa-messages.ts +17 -0
  320. package/src/ssr/index.ts +8 -0
  321. package/src/ssr/layout-context.ts +1 -1
  322. package/src/ssr/page-trail-runtime.ts +3 -1
  323. package/src/ssr/platform-messages.ts +9 -1
  324. package/src/ssr/pwa-messages.ts +10 -2
  325. package/src/ssr/rail-navigation.ts +5 -1
  326. package/src/ssr/settings-draft.ts +153 -0
  327. package/src/ssr/shell-data.tsx +2 -0
  328. package/src/styles/global.css +1 -0
  329. package/src/styles/profile-picture.css +270 -0
  330. package/src/styles/utilities-navigation.css +6 -0
  331. package/src/workflows/ai/policy.ts +49 -0
  332. package/src/workflows/ai/runtime.ts +131 -21
  333. package/src/workflows/ai/store.ts +283 -35
  334. package/src/workflows/ai/types.ts +8 -2
  335. package/src/workflows/ai-actions.ts +148 -120
  336. package/src/workflows/authoring.ts +31 -0
  337. package/src/workflows/contracts.ts +1 -0
  338. package/src/workflows/definition.ts +2 -0
  339. package/src/workflows/editor.ts +15 -0
  340. package/src/workflows/index.ts +2 -1
  341. package/src/workflows/language/compiler.ts +34 -3
  342. package/src/workflows/runtime/executor.ts +132 -33
  343. package/src/workflows/runtime/index.ts +2 -0
  344. package/src/workflows/runtime/ports.ts +5 -1
  345. package/src/workflows/store/actions.ts +1 -1
  346. package/src/workflows/store/definitions.ts +5 -0
  347. package/src/workflows/store/runs.ts +5 -3
  348. package/src/workflows/store/worker.ts +7 -2
  349. package/src/ai/model-pricing.ts +0 -43
  350. package/src/ai/model-request-settings.ts +0 -98
  351. package/src/ai/settings-messages.ts +0 -129
  352. package/src/shared/ai-model-request-settings.ts +0 -21
@@ -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(
@@ -763,6 +785,11 @@ export const UniversalSearchDataSchema = z.array(CloudResourceViewSchema).max(10
763
785
  export type UniversalSearchData = z.infer<typeof UniversalSearchDataSchema>;
764
786
 
765
787
  export type CapabilityExecutionContext = {
788
+ /** One read-only cross-app dependency under this invocation, through Core. */
789
+ query?: <T extends z.ZodType>(
790
+ invocation: { appId: string; capabilityId: string; input: unknown; signal?: AbortSignal },
791
+ dataSchema: T,
792
+ ) => Promise<import("../capabilities/types").CapabilityClientResult<z.output<T>>>;
766
793
  /**
767
794
  * Resolved request authority, supplied by the framework for execution, review
768
795
  * and streams. Optional for existing in-process callers; authority-aware
@@ -788,15 +815,25 @@ export type CapabilityExecutionContext = {
788
815
  requestId: string;
789
816
  /** Cloud surface that invoked this capability. */
790
817
  origin: CapabilityOrigin;
818
+ /** Framework-derived visible provenance; absent only in manually constructed contexts. */
819
+ via?: CapabilityVia;
791
820
  /** Caller cancellation or shared dispatch deadline; forward it to expensive work. */
792
821
  signal: AbortSignal;
793
822
  };
794
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
+
795
828
  export type CapabilityResourceTypeDefinition = {
796
829
  title: string;
797
830
  description: string;
798
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;
799
834
  reader?: string;
835
+ /** Root-relative pages recognized when a person pastes a Cloud link. Requires a reader. */
836
+ pages?: readonly string[];
800
837
  };
801
838
 
802
839
  export type CapabilitySearchTagDefinition = {
@@ -850,6 +887,7 @@ export type CapabilityOperationPresentationTranslation = {
850
887
  };
851
888
 
852
889
  export type CapabilityPresentationTranslation = {
890
+ events?: Readonly<Record<string, { title?: string; description?: string }>>;
853
891
  types?: Readonly<Record<string, { title?: string; description?: string }>>;
854
892
  queries?: Readonly<Record<string, CapabilityOperationPresentationTranslation>>;
855
893
  actions?: Readonly<Record<string, CapabilityOperationPresentationTranslation>>;
@@ -940,6 +978,8 @@ type CapabilityDefinitionCatalog<T> = Readonly<Record<string, T>>;
940
978
  /** Offers files to other apps: local IDs of the list Query, the read Query, and the optional save Action. */
941
979
  export type CapabilityFileProviderDeclaration = { list: string; read: string; save?: string };
942
980
 
981
+ export type CapabilityCalendarSourceDeclaration = { calendars: string; events: string; update?: string; move?: string; delete?: string };
982
+
943
983
  export type CapabilityDefinitions = {
944
984
  protocolVersion: typeof CAPABILITY_PROTOCOL_VERSION;
945
985
  presentation?: CapabilityPresentationCatalog;
@@ -948,13 +988,28 @@ export type CapabilityDefinitions = {
948
988
  actions?: CapabilityDefinitionCatalog<CapabilityActionDefinition>;
949
989
  commands?: CapabilityDefinitionCatalog<CapabilityCommandDefinition>;
950
990
  fileProvider?: CapabilityFileProviderDeclaration;
991
+ calendarSource?: CapabilityCalendarSourceDeclaration;
992
+ events?: CapabilityDefinitionCatalog<AppEventDefinition>;
993
+ subscribes?: CapabilityDefinitionCatalog<AppEventSubscriptionDefinition>;
951
994
  };
952
995
 
953
996
  /**
954
997
  * Declares one app's public capability surface. Runtime compilation happens
955
998
  * in defineApp.start(), where the app id is available for namespacing.
956
999
  */
957
- export const defineCapabilities = <const T extends CapabilityDefinitions>(definitions: T): T => definitions;
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
+ }
958
1013
 
959
1014
  export const CapabilityResourceTypeManifestSchema = z
960
1015
  .object({
@@ -962,6 +1017,7 @@ export const CapabilityResourceTypeManifestSchema = z
962
1017
  title: z.string().min(1).max(120),
963
1018
  description: z.string().min(1).max(500),
964
1019
  icon: z.string().min(1).max(120).optional(),
1020
+ color: z.enum(CAPABILITY_RESOURCE_COLORS).optional(),
965
1021
  reader: CapabilityLocalIdSchema.optional(),
966
1022
  })
967
1023
  .strict();
@@ -1015,6 +1071,51 @@ export const CapabilityFileProviderManifestSchema = z
1015
1071
  .object({ list: CapabilityLocalIdSchema, read: CapabilityLocalIdSchema, save: CapabilityLocalIdSchema.optional() })
1016
1072
  .strict();
1017
1073
 
1074
+ export const CapabilityCalendarSourceManifestSchema = z
1075
+ .object({
1076
+ calendars: CapabilityLocalIdSchema,
1077
+ events: CapabilityLocalIdSchema,
1078
+ update: CapabilityLocalIdSchema.optional(),
1079
+ move: CapabilityLocalIdSchema.optional(),
1080
+ delete: CapabilityLocalIdSchema.optional(),
1081
+ })
1082
+ .strict();
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
+
1018
1119
  /** Keeps the sent entries this release reads completely. */
1019
1120
  const readEntries = <T extends z.ZodType>(values: unknown, entry: T): z.output<T>[] | null => {
1020
1121
  // Not a bounded list: leave the value to the manifest schema, which rejects it.
@@ -1041,6 +1142,8 @@ const readEntries = <T extends z.ZodType>(values: unknown, entry: T): z.output<T
1041
1142
  * - An unreadable policy or one inconsistent with derived approval, review, or effect flags leaves
1042
1143
  * out its Action. Policies for unknown Actions are dropped.
1043
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.
1044
1147
  * Every other entry stays available.
1045
1148
  */
1046
1149
  const readableManifest = (value: unknown): unknown => {
@@ -1051,6 +1154,9 @@ const readableManifest = (value: unknown): unknown => {
1051
1154
  const actions = readEntries(sent.actions, CapabilityActionManifestSchema);
1052
1155
  const commands = readEntries(sent.commands, CapabilityCommandManifestSchema);
1053
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;
1054
1160
  const typeIds = new Set(types.map((type) => type.localId));
1055
1161
  const readableQueries = queries.filter((query) => query.universalSearch?.scopeTypes?.every((type) => typeIds.has(type)) ?? true);
1056
1162
  const queryIds = new Set(readableQueries.map((query) => query.localId));
@@ -1074,11 +1180,27 @@ const readableManifest = (value: unknown): unknown => {
1074
1180
  policies[action.localId] = policy;
1075
1181
  return true;
1076
1182
  });
1077
- const { fileProvider: sentProvider, policies: _policies, ...rest } = sent;
1183
+ const { calendarSource: sentCalendarSource, fileProvider: sentProvider, policies: _policies, resourcePages: sentPages, ...rest } = sent;
1078
1184
  const provider = CapabilityFileProviderManifestSchema.safeParse(sentProvider);
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
+ : [];
1079
1199
  return {
1080
1200
  ...rest,
1201
+ ...(sentPages !== undefined ? { resourcePages } : {}),
1081
1202
  ...(provider.success ? { fileProvider: provider.data } : {}),
1203
+ ...(calendar.success ? { calendarSource: calendar.data } : {}),
1082
1204
  types: types.map((type) => {
1083
1205
  if (!type.reader || queryIds.has(type.reader)) return type;
1084
1206
  const { reader: _missing, ...withoutReader } = type;
@@ -1088,6 +1210,16 @@ const readableManifest = (value: unknown): unknown => {
1088
1210
  actions: readableActions,
1089
1211
  ...(sentPolicies !== undefined ? { policies } : {}),
1090
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 } : {}),
1091
1223
  };
1092
1224
  };
1093
1225
 
@@ -1107,7 +1239,14 @@ export const CapabilityManifestSchema = z.preprocess(
1107
1239
  commands: z.array(CapabilityCommandManifestSchema).max(MAX_MANIFEST_ENTRIES),
1108
1240
  // Optional without a default: a manifest without a provider keeps the shape and hash of earlier releases.
1109
1241
  fileProvider: CapabilityFileProviderManifestSchema.optional(),
1242
+ calendarSource: CapabilityCalendarSourceManifestSchema.optional(),
1110
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(),
1111
1250
  }),
1112
1251
  );
1113
1252
 
@@ -1132,12 +1271,34 @@ export const resolveCapabilityResourceReader = (manifest: CapabilityManifest, re
1132
1271
  };
1133
1272
 
1134
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
+
1135
1295
  export const CapabilityCatalogAppSchema = z.object({
1136
1296
  appId: CapabilityAppIdSchema,
1137
1297
  appName: z.string().min(1).max(200),
1138
1298
  appIcon: z.string().min(1).max(120),
1139
1299
  appDescription: z.string().max(1000),
1140
1300
  manifest: CapabilityManifestSchema,
1301
+ events: z.array(CapabilityEventCatalogEntrySchema).max(MAX_MANIFEST_ENTRIES).optional(),
1141
1302
  });
1142
1303
 
1143
1304
  export const CapabilityCatalogSchema = z.object({
@@ -55,6 +55,8 @@ export type GeoAdminUsage = {
55
55
  shares: Array<{ group: GeoUsageGroup; credits: number }>;
56
56
  /** Access subjects whose use today reached the per-person daily limit. */
57
57
  peopleAtLimit: number;
58
+ /** Routing estimates exceeded this month; details are in the audit view. */
59
+ routeCostOverruns: number;
58
60
  timezone: string;
59
61
  };
60
62
 
@@ -2,14 +2,6 @@ import { z } from "zod";
2
2
  import { GEO_MAX_POLYLINE_CHARS, GeoAddressSchema, GeoGeocodeInputSchema, GeoPointSchema, GeoProvenanceSchema } from "./geo";
3
3
 
4
4
  export const GEO_ROUTE_MAX_STOPS = 26;
5
- export const GeoRouteDistanceAllowanceSchema = z
6
- .number()
7
- .int()
8
- .min(1)
9
- .max(1_000_000_000)
10
- .describe(
11
- "Reference distance allowance in meters per leg; defaults to 500000. The whole chain reserves at this allowance. This is not a guaranteed provider bill ceiling. A larger actual route is charged and stops the chain.",
12
- );
13
5
  const stopId = z.string().min(1).max(100).describe("Stable stop ID, unique within this route.");
14
6
  export const GeoRouteOptionsSchema = z
15
7
  .object({
@@ -46,7 +38,6 @@ export const GeoRouteLegOptionsSchema = z
46
38
  .strict();
47
39
  export const GeoRoutePlanSchema = z
48
40
  .object({
49
- distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema.optional(),
50
41
  stops: z.array(GeoRouteStopSchema).min(2).max(GEO_ROUTE_MAX_STOPS).describe("2–26 stops in travel order, with unique IDs."),
51
42
  options: GeoRouteOptionsSchema.optional().describe("Default options for the route."),
52
43
  legOptions: z
@@ -72,7 +63,6 @@ export const GeoRouteLegSchema = z
72
63
  .strict();
73
64
  export const GeoRouteSchema = z
74
65
  .object({
75
- distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema,
76
66
  stops: z.array(GeoRouteResolvedStopSchema).min(2).max(GEO_ROUTE_MAX_STOPS).describe("Resolved stops in current travel order."),
77
67
  options: GeoRouteOptionsSchema.describe("Travel options; mode and avoid override only supplied fields."),
78
68
  legs: z.array(GeoRouteLegSchema).min(1).max(25).describe("Independent stopover legs in travel order."),
@@ -122,7 +112,6 @@ export const GeoRouteUpdateInputSchema = z
122
112
  .object({
123
113
  route: GeoRouteSchema,
124
114
  changes: GeoRouteChangesSchema.describe("One stateless route edit to preview."),
125
- distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema.optional(),
126
115
  })
127
116
  .strict();
128
117
  export const GeoRouteUpdateDataSchema = z
@@ -188,12 +188,24 @@ export const GeoErrorDetailsSchema = z.discriminatedUnion("code", [
188
188
  z
189
189
  .object({
190
190
  code: z.literal("GEO_COST_OVERRUN"),
191
- distanceAllowanceMeters: z.number().positive(),
191
+ adminHint: z.string().optional(),
192
+ estimatedDistanceMeters: z.number().nonnegative(),
193
+ reservedCredits: z.number().nonnegative(),
194
+ actualCredits: z.number().nonnegative(),
192
195
  actualDistanceMeters: z.number().nonnegative(),
193
196
  costs: GeoCostsSchema,
194
197
  })
195
198
  .strict(),
196
- z.object({ code: z.literal("GEO_BUDGET_EXHAUSTED"), scope: z.enum(["person", "cloud"]), resetsAt: z.iso.datetime() }).strict(),
199
+ z
200
+ .object({
201
+ code: z.literal("GEO_BUDGET_EXHAUSTED"),
202
+ scope: z.enum(["person", "cloud"]),
203
+ resetsAt: z.iso.datetime(),
204
+ requiredCredits: z.number().nonnegative().optional(),
205
+ availableCredits: z.number().optional(),
206
+ shortfallCredits: z.number().nonnegative().optional(),
207
+ })
208
+ .strict(),
197
209
  z.object({ code: z.literal("GEO_BUSY"), retryAfterSeconds: z.number().positive(), costs: GeoCostsSchema.optional() }).strict(),
198
210
  z
199
211
  .object({
@@ -2,6 +2,10 @@ 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";
6
+ export * from "./avatar";
7
+ export * from "./calendar-source";
8
+ export * from "./calendar-view";
5
9
  export * from "./capabilities";
6
10
  export * from "./capability-compatibility";
7
11
  export * from "./commands";
@@ -12,6 +16,7 @@ export * from "./geo-admin";
12
16
  export * from "./geo-map";
13
17
  export * from "./geo-route";
14
18
  export * from "./geoip";
19
+ export * from "./installable";
15
20
  export * from "./notification-types";
16
21
  export * from "./outgoing-mail";
17
22
  export * from "./panel-context";
@@ -23,6 +28,7 @@ export { RailAccessSchema, type RailAdminEntry, RailAdminInputSchema, RailAdminS
23
28
  export type { RailShortcut } from "./rail-preferences";
24
29
  export * from "./registry";
25
30
  export * from "./resource-clipboard";
31
+ export { type ResolvedCloudResourceUrl, ResolvedCloudResourceUrlSchema } from "./resource-resolution";
26
32
  export * from "./seen-hints";
27
33
  export * from "./shared";
28
34
  export * from "./user-notifications";
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+
3
+ /** One independently installable surface, separate from the Cloud mobile app's `pwa` parts. */
4
+ export const AppInstallableSchema = z.strictObject({
5
+ path: z.string(),
6
+ name: z.string().trim().min(1),
7
+ shortName: z.string().trim().min(1),
8
+ icon: z.string().regex(/^ti ti-[a-z0-9]+(?:-[a-z0-9]+)*$/),
9
+ themeColor: z.strictObject({
10
+ light: z.string().regex(/^#[0-9a-f]{6}$/i),
11
+ dark: z.string().regex(/^#[0-9a-f]{6}$/i),
12
+ }),
13
+ display: z.literal("standalone"),
14
+ });
15
+ export type AppInstallable = z.infer<typeof AppInstallableSchema>;
16
+
17
+ /** No decoding or URL normalization: declarations must already be canonical. */
18
+ export const isInstallablePath = (appId: string, path: string): boolean =>
19
+ /^[a-z][a-z0-9-]*$/.test(appId) &&
20
+ path.startsWith(`/app/${appId}/`) &&
21
+ /^\/app\/[a-z][a-z0-9-]*\/(?:[a-z0-9][a-z0-9-]*\/)+$/.test(path) &&
22
+ !path.split("/").slice(3).includes("help");
23
+
24
+ export const installableContains = (scope: string, path: string): boolean => path === scope.slice(0, -1) || path.startsWith(scope);
25
+
26
+ /** Root and /app are routing fallbacks, not evidence that an app owns a surface. */
27
+ export const validateInstallable = (appId: string, routes: readonly string[], value: unknown): AppInstallable | null => {
28
+ const parsed = AppInstallableSchema.safeParse(value);
29
+ if (!parsed.success || !isInstallablePath(appId, parsed.data.path)) return null;
30
+ const ownRoot = `/app/${appId}`;
31
+ const covered = routes.some((route) => {
32
+ const prefix = route.replace(/\/$/, "");
33
+ return (
34
+ /^\/app\/[a-z][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*$/.test(prefix) &&
35
+ (prefix === ownRoot || prefix.startsWith(`${ownRoot}/`)) &&
36
+ installableContains(`${prefix}/`, parsed.data.path)
37
+ );
38
+ });
39
+ return covered ? parsed.data : null;
40
+ };
@@ -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 complete `label` and `description` declaration. */
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
- if (!catalog || !requestedLocale) return { label: definition.label, description: definition.description };
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
- if (hasDuplicates(recommended) || hasDuplicates(required)) {
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({ recommended: Object.freeze(recommended), required: Object.freeze(required) }),
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);
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { AVATAR_KINDS, PersonAvatarSchema, RobotAvatarSchema } from "./avatar";
2
3
 
3
4
  /**
4
5
  * Schemas for account profile / password / request inputs. Live in core
@@ -55,6 +56,37 @@ export const UpdateAvatarResponseSchema = z.object({
55
56
  });
56
57
  export type UpdateAvatarResponse = z.infer<typeof UpdateAvatarResponseSchema>;
57
58
 
59
+ /**
60
+ * One save of the profile picture dialog: store and show a photo or a built avatar, or switch to a stored picture.
61
+ * The photo stays stored while another kind shows; only the active picture is served.
62
+ */
63
+ export const SaveAvatarSchema = z.union([
64
+ z.object({ dataUrl: AvatarDataUrlSchema }).strict(),
65
+ z.object({ built: PersonAvatarSchema }).strict(),
66
+ z.object({ use: z.enum(AVATAR_KINDS) }).strict(),
67
+ ]);
68
+ export type SaveAvatar = z.infer<typeof SaveAvatarSchema>;
69
+
70
+ /** A service account always shows a picture, so it switches only between its photo and its robot. */
71
+ export const SaveServiceAccountAvatarSchema = z.union([
72
+ z.object({ dataUrl: AvatarDataUrlSchema }).strict(),
73
+ z.object({ built: RobotAvatarSchema }).strict(),
74
+ z.object({ use: z.enum(["photo", "built"]) }).strict(),
75
+ ]);
76
+ export type SaveServiceAccountAvatar = z.infer<typeof SaveServiceAccountAvatarSchema>;
77
+
78
+ export const AvatarStateSchema = z.object({
79
+ kind: z.enum(AVATAR_KINDS),
80
+ built: z.union([PersonAvatarSchema, RobotAvatarSchema]).nullable(),
81
+ hasPhoto: z.boolean(),
82
+ /** The stored photo as a data URL, also while another kind shows; only the account holder or an administrator reads it. */
83
+ photo: AvatarDataUrlSchema.nullable(),
84
+ avatarHash: z.string().nullable(),
85
+ });
86
+ export type AvatarStateResponse = z.infer<typeof AvatarStateSchema>;
87
+
88
+ export const SaveAvatarResponseSchema = AvatarStateSchema.extend({ message: z.string() });
89
+
58
90
  export const ChangePasswordSchema = z
59
91
  .object({
60
92
  currentPassword: z.string().min(1),
@@ -0,0 +1,14 @@
1
+ import type { AppRegistryEntry } from "./registry";
2
+
3
+ /** Unnamed development builds do not identify a release. */
4
+ export const pwaBuild = (runtime: { release: string; version?: string } | undefined): string | undefined =>
5
+ runtime && !["", "development", "local"].includes(runtime.release) ? `${runtime.version ?? ""}:${runtime.release}` : undefined;
6
+
7
+ /** Only page owners and Core's shared assets can change a mobile document. */
8
+ export const pwaBuilds = (apps: readonly AppRegistryEntry[]): Record<string, string> =>
9
+ Object.fromEntries(
10
+ apps.flatMap((app) => {
11
+ const build = pwaBuild(app.runtime);
12
+ return build && (app.id === "core" || app.id === "pwa" || app.pwa) ? [[app.id, build]] : [];
13
+ }),
14
+ );