@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
@@ -1,6 +1,8 @@
1
+ import type { SQL } from "bun";
1
2
  import { z } from "zod";
2
3
  import type { RequestAuthority } from "../server/middleware/auth";
3
- import { type Role, RoleSchema } from "./shared";
4
+ import type { ResourceAccessAdapter } from "../server/services/access";
5
+ import { AccessEntrySchema, type Role, RoleSchema } from "./shared";
4
6
 
5
7
  const publicRoutes = z
6
8
  .array(
@@ -21,22 +23,41 @@ export const AppAccessRuleSchema = z.union([
21
23
  z.object({ kind: z.literal("signedIn"), publicRoutes }).strict(),
22
24
  z.object({ kind: z.literal("noGuests"), publicRoutes }).strict(),
23
25
  z.object({ kind: z.literal("roles"), roles: z.array(RoleSchema).min(1).readonly(), publicRoutes }).strict(),
26
+ z.object({ kind: z.literal("resources"), adapters: z.array(z.string().min(1)).min(1).readonly(), publicRoutes }).strict(),
24
27
  z.object({ kind: z.literal("grants"), until: FixedAppAccessRuleSchema.optional(), publicRoutes }).strict(),
25
28
  ]);
26
29
  /** Serializable entry rule evaluated by the platform; never application callback code. */
27
30
  export type AppAccessRule = z.infer<typeof AppAccessRuleSchema>;
28
- /** The optional management restriction runs in the owning application, in addition to a management grant. */
29
- export type AppAccessDeclaration = AppAccessRule & {
30
- canManage?: (authority: RequestAuthority) => boolean | Promise<boolean>;
31
+ /**
32
+ * The optional management restriction runs in the owning application, in addition to a management grant.
33
+ * SQL-backed callbacks and domain services they call must use the supplied db: edits hold its transaction connection.
34
+ */
35
+ export type AppAccessDeclaration = (
36
+ | Exclude<AppAccessRule, { kind: "resources" }>
37
+ | {
38
+ kind: "resources";
39
+ adapters: Readonly<Record<string, ResourceAppAccessAdapter>>;
40
+ publicRoutes?: readonly string[];
41
+ }
42
+ ) & {
43
+ canManage?: (authority: RequestAuthority, context: { db: SQL }) => boolean | Promise<boolean>;
31
44
  };
45
+ /** An existence query is required for resource-derived app access; list() cannot answer it without a resource scan. */
46
+ export type ResourceAppAccessAdapter = Required<Pick<ResourceAccessAdapter, "hasAnyRight">>;
32
47
  type PublicOptions = { publicRoutes?: readonly string[] };
33
48
  export const appAccess = {
49
+ resources: (
50
+ options: PublicOptions & { adapters: Readonly<Record<string, ResourceAppAccessAdapter>> },
51
+ ): Extract<AppAccessDeclaration, { kind: "resources" }> => ({
52
+ kind: "resources",
53
+ ...options,
54
+ }),
34
55
  signedIn: (options: PublicOptions = {}): AppAccessRule & { kind: "signedIn" } => ({ kind: "signedIn", ...options }),
35
56
  noGuests: (options: PublicOptions = {}): AppAccessRule & { kind: "noGuests" } => ({ kind: "noGuests", ...options }),
36
57
  roles: (roles: readonly Role[], options: PublicOptions = {}): AppAccessRule & { kind: "roles" } => ({ kind: "roles", roles, ...options }),
37
58
  grants: (
38
59
  options: PublicOptions & { until?: FixedAppAccessRule; canManage?: AppAccessDeclaration["canManage"] } = {},
39
- ): AppAccessDeclaration => ({ kind: "grants", ...options }),
60
+ ): Extract<AppAccessDeclaration, { kind: "grants" }> => ({ kind: "grants", ...options }),
40
61
  };
41
62
 
42
63
  export const compileAppAccess = (declaration: AppAccessDeclaration | undefined): AppAccessRule | undefined => {
@@ -44,5 +65,31 @@ export const compileAppAccess = (declaration: AppAccessDeclaration | undefined):
44
65
  const { canManage, ...rule } = declaration;
45
66
  if (canManage && (rule.kind !== "grants" || typeof canManage !== "function"))
46
67
  throw new Error("Only configurable app access accepts canManage");
68
+ if (rule.kind === "resources") {
69
+ if (Array.isArray(rule.adapters)) throw new Error("Declare resource adapters, not registry metadata");
70
+ for (const adapter of Object.values(rule.adapters)) {
71
+ if (typeof adapter.hasAnyRight !== "function") throw new Error("Resource app access requires hasAnyRight on every adapter");
72
+ }
73
+ return AppAccessRuleSchema.parse({ ...rule, adapters: Object.keys(rule.adapters) });
74
+ }
47
75
  return AppAccessRuleSchema.parse(rule);
48
76
  };
77
+
78
+ export const AppAccessSnapshotSchema = z.object({
79
+ appId: z.string(),
80
+ name: z.string(),
81
+ entries: z.array(
82
+ AccessEntrySchema.extend({
83
+ id: z.union([z.uuid(), z.literal("inherited-authenticated")]),
84
+ groupMemberCount: z.number().int().nonnegative().optional(),
85
+ /** Platform eligibility for presentation; the server still checks any app-owned restriction. */
86
+ effectiveManager: z.boolean().optional(),
87
+ }),
88
+ ),
89
+ ownEntryIds: z.array(z.string()),
90
+ configured: z.boolean(),
91
+ hasEffectiveManager: z.boolean(),
92
+ inheritedSignedIn: z.boolean(),
93
+ canUse: z.boolean(),
94
+ });
95
+ export type AppAccessSnapshot = z.infer<typeof AppAccessSnapshotSchema>;
@@ -265,7 +265,14 @@ export const AppPendingLoginSchema = z
265
265
  })
266
266
  .strict();
267
267
  export const AppPairingPayloadSchema = z
268
- .object({ protocol: z.literal(APP_APPROVAL_PROTOCOL), issuer: z.string().url(), pairingId: Id, secret: Secret, expiresAt: DateTime })
268
+ .object({
269
+ protocol: z.literal(APP_APPROVAL_PROTOCOL),
270
+ issuer: z.string().url(),
271
+ pairingId: Id,
272
+ secret: Secret,
273
+ expiresAt: DateTime,
274
+ label: z.string().trim().min(1).max(80).optional(),
275
+ })
269
276
  .strict();
270
277
  export type AppPairingPayload = z.infer<typeof AppPairingPayloadSchema>;
271
278
 
@@ -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)}`;
@@ -1,5 +1,6 @@
1
1
  import type { Sync } from "@k2b/sync";
2
2
  import type { AppAccessRule } from "./app-access";
3
+ import type { AppInstallable } from "./installable";
3
4
  import type { PlatformPermission } from "./outgoing-mail";
4
5
  import type { AppRegistryHelpSummary } from "./registry";
5
6
  import type { Role } from "./shared";
@@ -49,9 +50,16 @@ export type AppAdminNavigationLink = {
49
50
  icon: string;
50
51
  };
51
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
+
52
57
  export type AppAdminNavigationGroup = {
53
- /** Contribute links to the platform AI section instead of a separate group. */
54
- section?: "ai";
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;
55
63
  /** Stable key used only to localize this presentation group. */
56
64
  id?: string;
57
65
  label: string;
@@ -108,6 +116,10 @@ export type AppPwaPart = {
108
116
  href: string;
109
117
  /** Coarse visibility in the mobile app, like `nav.requiresRoles`. Routes and services still authorize. */
110
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;
111
123
  };
112
124
 
113
125
  /** One same-origin panel page contributed by an application. Routes and services still authorize. */
@@ -126,6 +138,8 @@ export type AppMeta = {
126
138
  access?: AppAccessRule;
127
139
  platformPermissions?: readonly PlatformPermission[];
128
140
  id: string;
141
+ /** Shared object-store package; omitted means the app ID. */
142
+ package?: string;
129
143
  name: string;
130
144
  icon: string;
131
145
  description: string;
@@ -158,6 +172,8 @@ export type AppMeta = {
158
172
  searchLinks?: readonly AppSearchLink[];
159
173
  /** The app's pages in the installable mobile app (preview), at `href` = `/pwa/<id>`. */
160
174
  pwa?: AppPwaPart;
175
+ /** One standalone installation under this app's own declared route. */
176
+ installable?: AppInstallable;
161
177
  panel?: AppPanel;
162
178
  /**
163
179
  * Dashboard widget endpoints this app exposes. Each entry references an
@@ -177,12 +193,15 @@ export type AppMeta = {
177
193
 
178
194
  /**
179
195
  * Response of an app's `nav.badge` route: how many items wait for the signed-in
180
- * person. A positive safe integer shows a count on the app's icon in the rail
181
- * and the app grid (above 99 as "99+"); `0`, a non-200 response, or anything
182
- * 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.
183
199
  */
184
200
  export type AppNavBadge = { count: number };
185
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
+
186
205
  /** A widget endpoint and its board presentation; the handler owns resource authorization. */
187
206
  export type WidgetEndpoint = {
188
207
  /** Unique-within-the-app id, e.g. "open-requests". */
@@ -0,0 +1,274 @@
1
+ import { z } from "zod";
2
+ import { type CapabilityManifest, CapabilitySemanticLinkSchema, CloudResourceRefSchema } from "./capabilities";
3
+ import { type CapabilityContract, type CapabilityContractIssue, capabilityContractIssues } from "./capability-compatibility";
4
+
5
+ /** Elapsed time, matching Spaces agenda's public 31-day request budget. */
6
+ export const CALENDAR_SOURCE_MAX_RANGE_MS = 31 * 24 * 60 * 60 * 1000;
7
+ /** Spaces agenda's 100-item page; providers must also fit the 256 KiB capability result budget. */
8
+ export const CALENDAR_SOURCE_PAGE_LIMIT = 100;
9
+ /** Stale writes fail with HTTP 409. Sources never overwrite a different version. */
10
+ export const CALENDAR_VERSION_CONFLICT = "CALENDAR_VERSION_CONFLICT";
11
+
12
+ // Same opaque ID/cursor and display-text budgets as file providers and capability links.
13
+ // Field bounds limit individual allocations; they do not replace the 256 KiB page budget.
14
+ const IdSchema = z.string().min(1).max(2048).describe("Opaque source identifier or version.");
15
+ const CursorSchema = z.string().min(1).max(16384).describe("Opaque continuation cursor; keep the other inputs unchanged.");
16
+ const TimestampSchema = z.string().datetime({ offset: true }).describe("Timestamp with an explicit UTC offset.");
17
+ const DateSchema = z.iso.date().describe("Date in the source configured date/time zone.");
18
+ const TimeZoneSchema = z
19
+ .string()
20
+ .min(1)
21
+ .max(200)
22
+ .refine((zone) => {
23
+ try {
24
+ // Intl also accepts fixed offsets; the contract requires a named IANA zone.
25
+ return !/^[+-]/.test(new Intl.DateTimeFormat("en", { timeZone: zone }).resolvedOptions().timeZone);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }, "Expected an IANA time zone")
30
+ .describe("IANA time zone, or null for an instant without a display zone.");
31
+ const OpenSchema = CapabilitySemanticLinkSchema.extend({ rel: z.literal("open") }).loose();
32
+ const LimitSchema = z.number().int().min(1).max(CALENDAR_SOURCE_PAGE_LIMIT).optional().describe("Maximum page size, between 1 and 100.");
33
+
34
+ export const CalendarListInputSchema = z.object({ cursor: CursorSchema.optional(), limit: LimitSchema }).strict();
35
+ export const CalendarDescriptorSchema = z
36
+ .object({
37
+ id: IdSchema,
38
+ name: z.string().min(1).max(200),
39
+ description: z.string().max(500).optional(),
40
+ color: z
41
+ .string()
42
+ .regex(/^#[0-9a-fA-F]{6}$/)
43
+ .optional(),
44
+ suggested: z.boolean(),
45
+ visibility: z.enum(["details", "busy"]),
46
+ writable: z.boolean(),
47
+ open: OpenSchema.optional(),
48
+ })
49
+ .loose();
50
+ export const CalendarListDataSchema = z
51
+ .object({ items: z.array(CalendarDescriptorSchema).max(CALENDAR_SOURCE_PAGE_LIMIT), next: CursorSchema.nullable() })
52
+ .loose();
53
+
54
+ /** Half-open [from, to): events overlap it; deadlines have a due point inside it. */
55
+ export const CalendarEventsInputSchema = z
56
+ .object({
57
+ calendarIds: z
58
+ .array(IdSchema)
59
+ .min(1)
60
+ .max(CALENDAR_SOURCE_PAGE_LIMIT)
61
+ .describe("Readable calendar IDs to include; unknown or unreadable IDs are omitted."),
62
+ from: TimestampSchema,
63
+ to: TimestampSchema,
64
+ cursor: CursorSchema.optional(),
65
+ limit: LimitSchema,
66
+ })
67
+ .strict()
68
+ .refine((input) => Date.parse(input.to) > Date.parse(input.from), "to must be after from")
69
+ .refine((input) => Date.parse(input.to) - Date.parse(input.from) <= CALENDAR_SOURCE_MAX_RANGE_MS, "Range exceeds 31 elapsed days");
70
+
71
+ const EventDateSchema = z
72
+ .object({ kind: z.literal("date").describe("Date-only time."), start: DateSchema, end: DateSchema })
73
+ .loose()
74
+ .refine((time) => time.end > time.start, "Date end must be exclusive and after start");
75
+ const EventInstantSchema = z
76
+ .object({
77
+ kind: z.literal("instant").describe("Time with an explicit offset."),
78
+ start: TimestampSchema,
79
+ end: TimestampSchema,
80
+ timeZone: TimeZoneSchema.nullable().describe("IANA display zone, or null."),
81
+ })
82
+ .loose()
83
+ .refine((time) => Date.parse(time.end) >= Date.parse(time.start), "End must not precede start");
84
+ /** Date-only values use the source's configured date/time zone; floating instants are forbidden. */
85
+ export const CalendarEventTimeSchema = z.discriminatedUnion("kind", [EventDateSchema, EventInstantSchema]);
86
+ const DueDateSchema = z.object({ kind: z.literal("date").describe("Date-only time."), date: DateSchema }).loose();
87
+ const DueInstantSchema = z
88
+ .object({
89
+ kind: z.literal("instant").describe("Time with an explicit offset."),
90
+ at: TimestampSchema,
91
+ timeZone: TimeZoneSchema.nullable().describe("IANA display zone, or null."),
92
+ })
93
+ .loose();
94
+ export const CalendarDueTimeSchema = z.discriminatedUnion("kind", [DueDateSchema, DueInstantSchema]);
95
+ export const CalendarOccurrenceSchema = z
96
+ .object({
97
+ seriesId: IdSchema,
98
+ originalStart: z.union([TimestampSchema, DateSchema]),
99
+ // Display only: the source expands recurrence. A 1000-character RRULE fits the capability display budget.
100
+ rule: z.string().max(1000).nullable(),
101
+ })
102
+ .loose();
103
+ const identity = { id: IdSchema, calendarId: IdSchema };
104
+ const details = {
105
+ ...identity,
106
+ title: z.string().max(500),
107
+ occurrence: CalendarOccurrenceSchema.nullable(),
108
+ ref: CloudResourceRefSchema.loose().nullable(),
109
+ open: OpenSchema.nullable(),
110
+ version: IdSchema,
111
+ };
112
+ const eventState = { status: z.enum(["confirmed", "cancelled"]), transparency: z.enum(["busy", "free"]) };
113
+ export const CalendarItemSchema = z.discriminatedUnion("type", [
114
+ z
115
+ .object({
116
+ type: z.literal("event"),
117
+ ...details,
118
+ time: CalendarEventTimeSchema,
119
+ location: z.string().max(500).nullable(),
120
+ ...eventState,
121
+ })
122
+ .loose(),
123
+ z.object({ type: z.literal("deadline"), ...details, due: CalendarDueTimeSchema, done: z.boolean() }).loose(),
124
+ z.object({ type: z.literal("busy"), ...identity, time: CalendarEventTimeSchema, ...eventState }).loose(),
125
+ ]);
126
+ export const CalendarEventsDataSchema = z
127
+ .object({ items: z.array(CalendarItemSchema).max(CALENDAR_SOURCE_PAGE_LIMIT), next: CursorSchema.nullable() })
128
+ .loose();
129
+
130
+ const writeIdentity = {
131
+ ...identity,
132
+ version: IdSchema,
133
+ scope: z.enum(["occurrence", "series"]).optional().describe("Recurring write target; the source requires a scope for recurring items."),
134
+ };
135
+ export const CalendarUpdateInputSchema = z
136
+ .object({
137
+ ...writeIdentity,
138
+ changes: z
139
+ .object({
140
+ title: z.string().max(500).optional().describe("Replacement title."),
141
+ location: z.string().max(500).nullable().optional().describe("Replacement location; null clears it."),
142
+ done: z.boolean().optional().describe("Replacement deadline completion state."),
143
+ })
144
+ .strict()
145
+ .describe("Fields to update; the source validates which apply to the item."),
146
+ })
147
+ .strict();
148
+ export const CalendarMoveInputSchema = z
149
+ .object({
150
+ ...writeIdentity,
151
+ time: z
152
+ .discriminatedUnion("kind", [EventDateSchema.strict(), EventInstantSchema.strict()])
153
+ .optional()
154
+ .describe("Replacement event time; mutually exclusive with due."),
155
+ due: z
156
+ .discriminatedUnion("kind", [DueDateSchema.strict(), DueInstantSchema.strict()])
157
+ .optional()
158
+ .describe("Replacement deadline due point; mutually exclusive with time."),
159
+ })
160
+ .strict()
161
+ .refine((input) => (input.time === undefined) !== (input.due === undefined), "Provide exactly one of time or due");
162
+ export const CalendarDeleteInputSchema = z.object(writeIdentity).strict();
163
+ export const CalendarUpdateDataSchema = z.object({ item: CalendarItemSchema }).loose();
164
+ export const CalendarMoveDataSchema = CalendarUpdateDataSchema;
165
+ export const CalendarDeleteDataSchema = z.object({}).loose();
166
+
167
+ /** Queries have no streams and openWorld:false. Reserved writes require idempotency keys and current versions. */
168
+ export const calendarSource = {
169
+ calendars: { kind: "query", input: CalendarListInputSchema, data: CalendarListDataSchema },
170
+ events: { kind: "query", input: CalendarEventsInputSchema, data: CalendarEventsDataSchema },
171
+ update: { kind: "action", idempotency: "required", input: CalendarUpdateInputSchema, data: CalendarUpdateDataSchema },
172
+ move: { kind: "action", idempotency: "required", input: CalendarMoveInputSchema, data: CalendarMoveDataSchema },
173
+ delete: { kind: "action", idempotency: "required", input: CalendarDeleteInputSchema, data: CalendarDeleteDataSchema },
174
+ } as const satisfies Record<string, CapabilityContract>;
175
+ export type CalendarSourceFunction = keyof typeof calendarSource;
176
+ export const CALENDAR_SOURCE_FUNCTIONS = [
177
+ "calendars",
178
+ "events",
179
+ "update",
180
+ "move",
181
+ "delete",
182
+ ] as const satisfies readonly CalendarSourceFunction[];
183
+ export type CalendarSourceIssue = CapabilityContractIssue & { function: CalendarSourceFunction; localId: string };
184
+ export const calendarSourceIssues = (manifest: CapabilityManifest): CalendarSourceIssue[] => {
185
+ const declaration = manifest.calendarSource;
186
+ if (!declaration) return [];
187
+ return CALENDAR_SOURCE_FUNCTIONS.flatMap((name) => {
188
+ const localId = declaration[name];
189
+ if (localId === undefined) return [];
190
+ const query = manifest.queries.find((operation) => operation.localId === localId);
191
+ const action = manifest.actions.find((operation) => operation.localId === localId);
192
+ const issues: CapabilityContractIssue[] = query
193
+ ? capabilityContractIssues(calendarSource[name], { kind: "query", operation: query })
194
+ : action
195
+ ? capabilityContractIssues(calendarSource[name], { kind: "action", operation: action })
196
+ : [{ code: "kind", path: "$", message: `No Query or Action named ${localId}` }];
197
+ if ((query ?? action)?.openWorld) issues.push({ code: "kind", path: "$", message: "Calendar operations must have openWorld: false" });
198
+ if (name === "delete" && action && !action.destructive)
199
+ issues.push({ code: "kind", path: "$", message: "Calendar delete must be destructive" });
200
+ return issues.map((issue) => ({ ...issue, function: name, localId }));
201
+ });
202
+ };
203
+
204
+ export type CalendarListInput = z.output<typeof CalendarListInputSchema>;
205
+ export type CalendarDescriptor = z.output<typeof CalendarDescriptorSchema>;
206
+ export type CalendarListData = z.output<typeof CalendarListDataSchema>;
207
+ export type CalendarEventsInput = z.output<typeof CalendarEventsInputSchema>;
208
+ export type CalendarEventTime = z.output<typeof CalendarEventTimeSchema>;
209
+ export type CalendarDueTime = z.output<typeof CalendarDueTimeSchema>;
210
+ export type CalendarOccurrence = z.output<typeof CalendarOccurrenceSchema>;
211
+ export type CalendarItem = z.output<typeof CalendarItemSchema>;
212
+ export type CalendarEventsData = z.output<typeof CalendarEventsDataSchema>;
213
+ export type CalendarUpdateInput = z.output<typeof CalendarUpdateInputSchema>;
214
+ export type CalendarMoveInput = z.output<typeof CalendarMoveInputSchema>;
215
+ export type CalendarDeleteInput = z.output<typeof CalendarDeleteInputSchema>;
216
+ export type CalendarUpdateData = z.output<typeof CalendarUpdateDataSchema>;
217
+ export type CalendarMoveData = z.output<typeof CalendarMoveDataSchema>;
218
+ export type CalendarDeleteData = z.output<typeof CalendarDeleteDataSchema>;
219
+
220
+ const projectTime = (time: CalendarEventTime): CalendarEventTime =>
221
+ time.kind === "date"
222
+ ? { kind: "date", start: time.start, end: time.end }
223
+ : { kind: "instant", start: time.start, end: time.end, timeZone: time.timeZone };
224
+ const projectOpen = (open: z.output<typeof OpenSchema>) => ({
225
+ rel: open.rel,
226
+ href: open.href,
227
+ ...(open.title === undefined ? {} : { title: open.title }),
228
+ });
229
+ /** Readers discard every unrecognized field, including nested fields and all details on busy items. */
230
+ export const projectCalendarItem = (item: CalendarItem): CalendarItem => {
231
+ const identity = { id: item.id, calendarId: item.calendarId };
232
+ if (item.type === "busy")
233
+ return { type: "busy", ...identity, time: projectTime(item.time), status: item.status, transparency: item.transparency };
234
+ const details = {
235
+ ...identity,
236
+ title: item.title,
237
+ version: item.version,
238
+ occurrence: item.occurrence && {
239
+ seriesId: item.occurrence.seriesId,
240
+ originalStart: item.occurrence.originalStart,
241
+ rule: item.occurrence.rule,
242
+ },
243
+ ref: item.ref && { type: item.ref.type, id: item.ref.id },
244
+ open: item.open && projectOpen(item.open),
245
+ };
246
+ return item.type === "event"
247
+ ? {
248
+ type: "event",
249
+ ...details,
250
+ time: projectTime(item.time),
251
+ location: item.location,
252
+ status: item.status,
253
+ transparency: item.transparency,
254
+ }
255
+ : {
256
+ type: "deadline",
257
+ ...details,
258
+ done: item.done,
259
+ due:
260
+ item.due.kind === "date"
261
+ ? { kind: "date", date: item.due.date }
262
+ : { kind: "instant", at: item.due.at, timeZone: item.due.timeZone },
263
+ };
264
+ };
265
+ export const projectCalendarDescriptor = (calendar: CalendarDescriptor): CalendarDescriptor => ({
266
+ id: calendar.id,
267
+ name: calendar.name,
268
+ suggested: calendar.suggested,
269
+ visibility: calendar.visibility,
270
+ writable: calendar.writable,
271
+ ...(calendar.description === undefined ? {} : { description: calendar.description }),
272
+ ...(calendar.color === undefined ? {} : { color: calendar.color }),
273
+ ...(calendar.open === undefined ? {} : { open: projectOpen(calendar.open) }),
274
+ });
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { CALENDAR_SOURCE_PAGE_LIMIT, CalendarDescriptorSchema, CalendarItemSchema } from "./calendar-source";
3
+ import { CapabilityAppIdSchema } from "./capabilities";
4
+
5
+ /** Interactive calendar request budget: six elapsed weeks. */
6
+ export const CALENDAR_VIEW_MAX_RANGE_MS = 42 * 24 * 60 * 60 * 1000;
7
+ export const CalendarViewRangeSchema = z
8
+ .object({ from: z.string().datetime({ offset: true }), to: z.string().datetime({ offset: true }) })
9
+ .strict()
10
+ .refine(({ from, to }) => Date.parse(to) > Date.parse(from), "to must be after from")
11
+ .refine(({ from, to }) => Date.parse(to) - Date.parse(from) <= CALENDAR_VIEW_MAX_RANGE_MS, "Range exceeds 42 elapsed days");
12
+ export const CalendarSourceErrorSchema = z.object({ code: z.string(), retryable: z.boolean() });
13
+ export const CalendarSourceStateSchema = z.enum(["ok", "pending", "failed", "truncated"]);
14
+ export const CalendarSourceSummarySchema = z.object({
15
+ appId: CapabilityAppIdSchema,
16
+ state: CalendarSourceStateSchema,
17
+ error: CalendarSourceErrorSchema.nullable(),
18
+ });
19
+ export const CalendarSourceListEntrySchema = z.object({
20
+ appId: CapabilityAppIdSchema,
21
+ name: z.string(),
22
+ icon: z.string().nullable(),
23
+ state: z.enum(["ok", "failed"]),
24
+ error: CalendarSourceErrorSchema.nullable(),
25
+ calendars: z.array(CalendarDescriptorSchema).max(CALENDAR_SOURCE_PAGE_LIMIT),
26
+ });
27
+ export const CalendarReadResultSchema = z.object({
28
+ items: z.array(CalendarItemSchema.and(z.object({ appId: CapabilityAppIdSchema }))),
29
+ sources: z.array(CalendarSourceSummarySchema),
30
+ });
31
+ export type CalendarSourceState = z.output<typeof CalendarSourceStateSchema>;
32
+ export type CalendarSourceSummary = z.output<typeof CalendarSourceSummarySchema>;
33
+ export type CalendarSourceListEntry = z.output<typeof CalendarSourceListEntrySchema>;
34
+ export type CalendarReadResult = z.output<typeof CalendarReadResultSchema>;
35
+ export type CalendarSelection = { appId: string; calendarId: string };