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